pax_global_header00006660000000000000000000000064136226507400014517gustar00rootroot0000000000000052 comment=1e8c9ab6c53bd05b01b38e250a7fc4797e351068 rumur-2020.02.17/000077500000000000000000000000001362265074000132645ustar00rootroot00000000000000rumur-2020.02.17/.cirrus.yml000066400000000000000000000020071362265074000153730ustar00rootroot00000000000000task: # only test the master branch and pull requests only_if: $CIRRUS_BRANCH == "master" || $CIRRUS_PR != "" # increase timeout to the maximum limit timeout_in: 120m install_script: pkg upgrade -y && pkg install -y bash bison cmake gmp libxml2 ninja z3 # we limit the test suite to a single thread because the Cirrus CI VMs claim # to have 2 CPUs but do not seem to give two concurrent processes enough CPU # time and we end up having some of the SMT tests time out test_script: uname -sr && python3 --version && mkdir build && cd build && cmake -G Ninja .. && cmake --build . && sudo cmake --build . -- install && ../tests/run-tests.py --jobs 1 matrix: - name: FreeBSD 13.0 freebsd_instance: image_family: freebsd-13-0-snap - name: FreeBSD 12.1 freebsd_instance: image_family: freebsd-12-1-snap - name: FreeBSD 12.0 freebsd_instance: image_family: freebsd-12-0 - name: FreeBSD 11.3 freebsd_instance: image_family: freebsd-11-3-snap rumur-2020.02.17/.gitignore000066400000000000000000000000061362265074000152500ustar00rootroot00000000000000*.swp rumur-2020.02.17/.travis.yml000066400000000000000000002127421362265074000154050ustar00rootroot00000000000000language: cpp branches: only: - master matrix: include: - name: Ubuntu Linux 14.04, GCC 4.7 (1/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.7 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 # force Python 3 to point to 3.6 instead of 3.4.3 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.7 && CXX=g++-4.7" # Fail on a compilation warning, emit debug symbols, support full back # traces. - CXXFLAGS="-Werror -g -fno-omit-frame-pointer" - name: Ubuntu Linux 14.04, GCC 4.7 (2/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.7 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.7 && CXX=g++-4.7" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer" - name: Ubuntu Linux 14.04, GCC 4.7 (3/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.7 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.7 && CXX=g++-4.7" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer" - name: Ubuntu Linux 14.04, GCC 4.7 (4/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.7 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=3001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.7 && CXX=g++-4.7" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer" - name: Ubuntu Linux 14.04, GCC 4.8 (1/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.8 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.8 && CXX=g++-4.8" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer" - name: Ubuntu Linux 14.04, GCC 4.8 (2/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.8 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.8 && CXX=g++-4.8" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer" - name: Ubuntu Linux 14.04, GCC 4.8 (3/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.8 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.8 && CXX=g++-4.8" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer" - name: Ubuntu Linux 14.04, GCC 4.8 (4/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.8 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=3001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.8 && CXX=g++-4.8" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer" - name: Ubuntu Linux 14.04, GCC 4.9 (1/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.9 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.9 && CXX=g++-4.9" # In addition to earlier options, enable UBSan, and abort execution on a # UBSan error. Note that enabling UBSan on GCC seems to require the GOLD # linker. - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 4.9 (2/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.9 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.9 && CXX=g++-4.9" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 4.9 (3/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.9 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.9 && CXX=g++-4.9" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 4.9 (4/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-4.9 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=3001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-4.9 && CXX=g++-4.9" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 5 (1/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-5 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-5 && CXX=g++-5" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 5 (2/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-5 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-5 && CXX=g++-5" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 5 (3/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-5 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-5 && CXX=g++-5" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 5 (4/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-5 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=3001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-5 && CXX=g++-5" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 5 (1/4) os: linux dist: bionic addons: apt: packages: - g++-5 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MAX_TEST=1000 - MATRIX_EVAL="CC=gcc-5 && CXX=g++-5" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 5 (2/4) os: linux dist: bionic addons: apt: packages: - g++-5 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="CC=gcc-5 && CXX=g++-5" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 5 (3/4) os: linux dist: bionic addons: apt: packages: - g++-5 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="CC=gcc-5 && CXX=g++-5" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 5 (4/4) os: linux dist: bionic addons: apt: packages: - g++-5 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=3001 - MATRIX_EVAL="CC=gcc-5 && CXX=g++-5" - CXXFLAGS="-Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 6 (1/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-6 && CXX=g++-6" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 6 (2/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-6 && CXX=g++-6" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 6 (3/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-6 && CXX=g++-6" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 6 (4/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=3001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-6 && CXX=g++-6" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 6 (1/4) os: linux dist: bionic addons: apt: packages: - g++-6 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MAX_TEST=1000 - MATRIX_EVAL="CC=gcc-6 && CXX=g++-6" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 6 (2/4) os: linux dist: bionic addons: apt: packages: - g++-6 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="CC=gcc-6 && CXX=g++-6" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 6 (3/4) os: linux dist: bionic addons: apt: packages: - g++-6 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="CC=gcc-6 && CXX=g++-6" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 6 (4/4) os: linux dist: bionic addons: apt: packages: - g++-6 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=3001 - MATRIX_EVAL="CC=gcc-6 && CXX=g++-6" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 7 (1/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-7 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-7 && CXX=g++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 7 (2/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-7 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-7 && CXX=g++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 7 (3/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-7 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-7 && CXX=g++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 7 (4/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-7 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=3001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-7 && CXX=g++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 7 (1/4) os: linux dist: bionic addons: apt: packages: - g++-7 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MAX_TEST=1000 - MATRIX_EVAL="CC=gcc-7 && CXX=g++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 7 (2/4) os: linux dist: bionic addons: apt: packages: - g++-7 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="CC=gcc-7 && CXX=g++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 7 (3/4) os: linux dist: bionic addons: apt: packages: - g++-7 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="CC=gcc-7 && CXX=g++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 7 (4/4) os: linux dist: bionic addons: apt: packages: - g++-7 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=3001 - MATRIX_EVAL="CC=gcc-7 && CXX=g++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 8 (1/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-8 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-8 && CXX=g++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 8 (2/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-8 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-8 && CXX=g++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 8 (3/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-8 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-8 && CXX=g++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 8 (4/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-8 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=3001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-8 && CXX=g++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 8 (1/4) os: linux dist: bionic addons: apt: packages: - g++-8 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MAX_TEST=1000 - MATRIX_EVAL="CC=gcc-8 && CXX=g++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 8 (2/4) os: linux dist: bionic addons: apt: packages: - g++-8 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="CC=gcc-8 && CXX=g++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 8 (3/4) os: linux dist: bionic addons: apt: packages: - g++-8 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="CC=gcc-8 && CXX=g++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 8 (4/4) os: linux dist: bionic addons: apt: packages: - g++-8 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=3001 - MATRIX_EVAL="CC=gcc-8 && CXX=g++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 9 (1/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-9 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-9 && CXX=g++-9" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 9 (2/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-9 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-9 && CXX=g++-9" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 9 (3/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-9 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-9 && CXX=g++-9" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, GCC 9 (4/4) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-9 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=3001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=gcc-9 && CXX=g++-9" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 9 (1/4) os: linux dist: bionic addons: apt: sources: # XXX: this PPA is not currently whitelisted on 18.04 # (https://github.com/travis-ci/apt-source-safelist/issues/410) - sourceline: 'ppa:ubuntu-toolchain-r/test' packages: - g++-9 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MAX_TEST=1000 - MATRIX_EVAL="CC=gcc-9 && CXX=g++-9" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 9 (2/4) os: linux dist: bionic addons: apt: sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' packages: - g++-9 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="CC=gcc-9 && CXX=g++-9" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 9 (3/4) os: linux dist: bionic addons: apt: sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' packages: - g++-9 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="CC=gcc-9 && CXX=g++-9" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC 9 (4/4) os: linux dist: bionic addons: apt: sources: - sourceline: 'ppa:ubuntu-toolchain-r/test' packages: - g++-9 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=3001 - MATRIX_EVAL="CC=gcc-9 && CXX=g++-9" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC multilib (1/4) os: linux dist: bionic addons: apt: packages: - g++-multilib - libc6-dev-i386 - libfl-dev - libxml2-utils - ninja-build - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="CC=gcc && CXX=g++" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC multilib (2/4) os: linux dist: bionic addons: apt: packages: - g++-multilib - libc6-dev-i386 - libfl-dev - libxml2-utils - ninja-build - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="CC=gcc && CXX=g++" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC multilib (3/4) os: linux dist: bionic addons: apt: packages: - g++-multilib - libc6-dev-i386 - libfl-dev - libxml2-utils - ninja-build - valgrind env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="CC=gcc && CXX=g++" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, GCC multilib (4/4) os: linux dist: bionic addons: apt: packages: - g++-multilib - libc6-dev-i386 - libfl-dev - libxml2-utils - ninja-build - valgrind env: - MIN_TEST=3001 - MATRIX_EVAL="CC=gcc && CXX=g++" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined -fuse-ld=gold" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 3.8 (1/5) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-precise-3.8 packages: - clang-3.8 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=500 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-3.8 && CXX=clang++-3.8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 3.8 (2/5) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-precise-3.8 packages: - clang-3.8 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=501 - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-3.8 && CXX=clang++-3.8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 3.8 (3/5) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-precise-3.8 packages: - clang-3.8 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=1500 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-3.8 && CXX=clang++-3.8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 3.8 (4/5) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-precise-3.8 packages: - clang-3.8 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1501 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-3.8 && CXX=clang++-3.8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 3.8 (5/5) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-precise-3.8 packages: - clang-3.8 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-3.8 && CXX=clang++-3.8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 3.9 (1/2) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-3.9 packages: - clang-3.9 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-3.9 && CXX=clang++-3.9" # We don't enable any sanitizers for this build because the Clang and LLVM # packages seem conflicting in Travis' Ubuntu image. More information: # https://github.com/travis-ci/travis-ci/issues/8026. - CXXFLAGS="-pedantic -Werror" - name: Ubuntu Linux 14.04, Clang 3.9 (2/2) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-3.9 packages: - clang-3.9 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-3.9 && CXX=clang++-3.9" - CXXFLAGS="-pedantic -Werror" - name: Ubuntu Linux 14.04, Clang 4.0 (1/5) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-4.0 packages: - clang-4.0 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=500 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-4.0 && CXX=clang++-4.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 4.0 (2/5) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-4.0 packages: - clang-4.0 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=501 - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-4.0 && CXX=clang++-4.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 4.0 (3/5) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-4.0 packages: - clang-4.0 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=1500 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-4.0 && CXX=clang++-4.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 4.0 (4/5) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-4.0 packages: - clang-4.0 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1501 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-4.0 && CXX=clang++-4.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 4.0 (5/5) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-4.0 packages: - clang-4.0 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-4.0 && CXX=clang++-4.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 4.0 (1/5) os: linux dist: bionic addons: apt: packages: - clang-4.0 - g++-6 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MAX_TEST=750 - MATRIX_EVAL="CC=clang-4.0 && CXX=clang++-4.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 4.0 (2/5) os: linux dist: bionic addons: apt: packages: - clang-4.0 - g++-6 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=751 - MAX_TEST=1500 - MATRIX_EVAL="CC=clang-4.0 && CXX=clang++-4.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 4.0 (3/5) os: linux dist: bionic addons: apt: packages: - clang-4.0 - g++-6 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=1501 - MAX_TEST=2250 - MATRIX_EVAL="CC=clang-4.0 && CXX=clang++-4.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 4.0 (4/5) os: linux dist: bionic addons: apt: packages: - clang-4.0 - g++-6 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=2251 - MAX_TEST=3000 - MATRIX_EVAL="CC=clang-4.0 && CXX=clang++-4.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 4.0 (5/5) os: linux dist: bionic addons: apt: packages: - clang-4.0 - g++-6 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=3001 - MATRIX_EVAL="CC=clang-4.0 && CXX=clang++-4.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 5.0 (1/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-5.0 packages: - clang-5.0 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-5.0 && CXX=clang++-5.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 5.0 (2/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-5.0 packages: - clang-5.0 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-5.0 && CXX=clang++-5.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 5.0 (3/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-5.0 packages: - clang-5.0 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-5.0 && CXX=clang++-5.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 5.0 (1/4) os: linux dist: bionic addons: apt: packages: - clang-5.0 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MAX_TEST=1000 - MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 5.0 (2/4) os: linux dist: bionic addons: apt: packages: - clang-5.0 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 5.0 (3/4) os: linux dist: bionic addons: apt: packages: - clang-5.0 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 5.0 (4/4) os: linux dist: bionic addons: apt: packages: - clang-5.0 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=3001 - MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 6.0 (1/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-6.0 packages: - clang-6.0 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-6.0 && CXX=clang++-6.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 6.0 (2/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-6.0 packages: - clang-6.0 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-6.0 && CXX=clang++-6.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 6.0 (3/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-6.0 packages: - clang-6.0 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-6.0 && CXX=clang++-6.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 6.0 (1/4) os: linux dist: bionic addons: apt: packages: - clang-6.0 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MAX_TEST=1000 - MATRIX_EVAL="CC=clang-6.0 && CXX=clang++-6.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 6.0 (2/4) os: linux dist: bionic addons: apt: packages: - clang-6.0 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="CC=clang-6.0 && CXX=clang++-6.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 6.0 (3/4) os: linux dist: bionic addons: apt: packages: - clang-6.0 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="CC=clang-6.0 && CXX=clang++-6.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 6.0 (4/4) os: linux dist: bionic addons: apt: packages: - clang-6.0 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=3001 - MATRIX_EVAL="CC=clang-6.0 && CXX=clang++-6.0" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 7 (1/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-7 packages: - clang-7 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-7 && CXX=clang++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 7 (2/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-7 packages: - clang-7 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-7 && CXX=clang++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 7 (3/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-7 packages: - clang-7 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-7 && CXX=clang++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 7 (1/4) os: linux dist: bionic addons: apt: packages: - clang-7 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MAX_TEST=1000 - MATRIX_EVAL="CC=clang-7 && CXX=clang++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 7 (2/4) os: linux dist: bionic addons: apt: packages: - clang-7 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="CC=clang-7 && CXX=clang++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 7 (3/4) os: linux dist: bionic addons: apt: packages: - clang-7 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="CC=clang-7 && CXX=clang++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 7 (4/4) os: linux dist: bionic addons: apt: packages: - clang-7 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=3001 - MATRIX_EVAL="CC=clang-7 && CXX=clang++-7" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 8 (1/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-8 packages: - clang-8 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MAX_TEST=1000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-8 && CXX=clang++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 8 (2/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-8 packages: - clang-8 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-8 && CXX=clang++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 14.04, Clang 8 (3/3) os: linux dist: trusty addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test - llvm-toolchain-trusty-8 packages: - clang-8 - g++-6 - libxml2-utils - ninja-build - python3.6 - valgrind env: - MIN_TEST=2001 - MATRIX_EVAL="sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && CC=clang-8 && CXX=clang++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 8 (1/4) os: linux dist: bionic addons: apt: packages: - clang-8 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MAX_TEST=1000 - MATRIX_EVAL="CC=clang-8 && CXX=clang++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 8 (2/4) os: linux dist: bionic addons: apt: packages: - clang-8 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=1001 - MAX_TEST=2000 - MATRIX_EVAL="CC=clang-8 && CXX=clang++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 8 (3/4) os: linux dist: bionic addons: apt: packages: - clang-8 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=2001 - MAX_TEST=3000 - MATRIX_EVAL="CC=clang-8 && CXX=clang++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: Ubuntu Linux 18.04, Clang 8 (4/4) os: linux dist: bionic addons: apt: packages: - clang-8 - libfl-dev - libxml2-utils - ninja-build - valgrind - z3 env: - MIN_TEST=3001 - MATRIX_EVAL="CC=clang-8 && CXX=clang++-8" - CXXFLAGS="-pedantic -Werror -g -fno-omit-frame-pointer -fsanitize=undefined -fno-sanitize-recover=undefined" - UBSAN_OPTIONS=print_stacktrace=1 - name: macOS, XCode 7.3.1 Clang, Homebrew os: osx osx_image: xcode7.3 env: # To quote Homebrew, "bison is keg-only, which means it was not symlinked # into /usr/local, because some formulae require a newer version of # bison." I do not know when this became the case as it was not # originally, but we'll force this. Also, we need to install Python 3.6 # via pyenv and force it into our $PATH because the Homebrew recipe for # Python 3.6 is broken on older MacOS. 3.6.0 is only available in beta # prior to the xcode8 image. - PATH=/usr/local/opt/bison/bin:/Users/travis/.pyenv/versions/3.6.0b1/bin:${PATH} # The version of Bison that comes with XCode 7.3 is too old to support # some of the flags/features we use. To install newer versions and make # them available to CMake we need to link them into the system # directories. - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && pyenv install 3.6.0b1 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 8.0 Clang, Homebrew os: osx osx_image: xcode8 env: - PATH=/usr/local/opt/bison/bin:/Users/travis/.pyenv/versions/3.6.0/bin:${PATH} # somehow the xcode8 image does not have pyenv, despite earlier and later # xcode images having it - MATRIX_EVAL="brew update && brew install bison ninja pyenv && brew link bison --force && pyenv install 3.6.0 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 8.3.3 Clang, Homebrew os: osx osx_image: xcode8.3 env: - PATH=/usr/local/opt/bison/bin:/Users/travis/.pyenv/versions/3.6.0/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && pyenv install 3.6.0 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 9.0 Clang, Homebrew os: osx osx_image: xcode9 env: - PATH=/usr/local/opt/bison/bin:/Users/travis/.pyenv/versions/3.6.0/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && pyenv install 3.6.0 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 9.1 Clang, Homebrew os: osx osx_image: xcode9.1 env: - PATH=/usr/local/opt/bison/bin:/Users/travis/.pyenv/versions/3.6.0/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && pyenv install 3.6.0 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 9.2 Clang, Homebrew os: osx osx_image: xcode9.2 env: - PATH=/usr/local/opt/bison/bin:/Users/travis/.pyenv/versions/3.6.0/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && pyenv install 3.6.0 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 9.3 Clang, Homebrew os: osx osx_image: xcode9.3 env: - PATH=/usr/local/opt/bison/bin:/Users/travis/.pyenv/versions/3.6.0/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && pyenv install 3.6.0 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 9.4 Clang, Homebrew os: osx osx_image: xcode9.4 env: - PATH=/usr/local/opt/bison/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 10.0 Clang, Homebrew os: osx osx_image: xcode10 env: - PATH=/usr/local/opt/bison/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 10.1 Clang, Homebrew os: osx osx_image: xcode10.1 env: - PATH=/usr/local/opt/bison/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 10.2 Clang, Homebrew os: osx osx_image: xcode10.2 env: - PATH=/usr/local/opt/bison/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 10.3 Clang, Homebrew os: osx osx_image: xcode10.3 env: - PATH=/usr/local/opt/bison/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 11.0 Clang, Homebrew os: osx osx_image: xcode11 env: - PATH=/usr/local/opt/bison/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 11.1 Clang, Homebrew os: osx osx_image: xcode11.1 env: - PATH=/usr/local/opt/bison/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 11.2 Clang, Homebrew os: osx osx_image: xcode11.2 env: - PATH=/usr/local/opt/bison/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 11.3 Clang, Homebrew os: osx osx_image: xcode11.3 env: - PATH=/usr/local/opt/bison/bin:${PATH} - MATRIX_EVAL="brew update && brew install bison ninja && brew link bison --force && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 7.3.1 Clang, Macports os: osx osx_image: xcode7.3 env: # without this, Macports will not run (see # https://trac.macports.org/ticket/55151) - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja python38 && sudo port select --set python3 python38 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 8.0 Clang, Macports os: osx osx_image: xcode8 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja python38 && sudo port select --set python3 python38 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 8.3.3 Clang, Macports os: osx osx_image: xcode8.3 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja python38 && sudo port select --set python3 python38 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 9.0 Clang, Macports os: osx osx_image: xcode9 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja python38 && sudo port select --set python3 python38 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 9.1 Clang, Macports os: osx osx_image: xcode9.1 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja python38 && sudo port select --set python3 python38 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 9.2 Clang, Macports os: osx osx_image: xcode9.2 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja python38 && sudo port select --set python3 python38 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 9.3 Clang, Macports os: osx osx_image: xcode9.3 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja python38 && sudo port select --set python3 python38 && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 9.4 Clang, Macports os: osx osx_image: xcode9.4 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 10.0 Clang, Macports os: osx osx_image: xcode10 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 10.1 Clang, Macports os: osx osx_image: xcode10.1 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 10.2 Clang, Macports os: osx osx_image: xcode10.2 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 10.3 Clang, Macports os: osx osx_image: xcode10.3 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 11.0 Clang, Macports os: osx osx_image: xcode11 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 11.1 Clang, Macports os: osx osx_image: xcode11.1 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 11.2 Clang, Macports os: osx osx_image: xcode11.2 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: macOS, XCode 11.3 Clang, Macports os: osx osx_image: xcode11.3 env: - COLUMNS=80 - PATH=/opt/local/bin:${PATH} - MATRIX_EVAL="./misc/install-macports.sh && sudo port -v selfupdate && sudo port -N install bison ninja && CC=clang && CXX=clang++" - CXXFLAGS="-fsanitize=address -Werror" - name: Ubuntu Linux 16.04, GCC 9, AFL os: linux dist: xenial addons: apt: sources: - deadsnakes - ubuntu-toolchain-r-test packages: - g++-9 - ninja-build - python3.6 env: # install AFL and let it catch crashes via core dumps - MATRIX_EVAL="echo core | sudo tee /proc/sys/kernel/core_pattern && sudo ln -sf /usr/bin/python3.6 /usr/bin/python3 && ./misc/install-afl.sh && CC=gcc-9 && CXX=afl-g++" # quit if we succeed exhaustively fuzzing (unlikely) - AFL_EXIT_WHEN_DONE=1 # suppress normal animated UI and use periodic text output - AFL_NO_UI=1 - CUSTOM_COMMAND="travis_wait 60 ../misc/afl-wrapper.sh" # This file initially contained entries to test GCC on macOS as well, but I # could not figure out how to get these working. One show stopper seems to be # that the available libgmps on macOS all seem to have been built with Clang in # a way that is ABI-incompatible with linking against GCC-compiled code. before_install: - if [ "$(uname -s)" = "Linux" ]; then travis_retry sudo apt-get -qq update; fi - eval "${MATRIX_EVAL}" script: - uname -sr && python3 --version && mkdir build && cd build && cmake -G Ninja .. && cmake --build . && sudo env "PATH=$PATH" cmake --build . -- install && if [ -z "${CUSTOM_COMMAND}" ]; then ../tests/run-tests.py; else eval "${CUSTOM_COMMAND}"; fi rumur-2020.02.17/CHANGELOG.rst000066400000000000000000001323251362265074000153130ustar00rootroot00000000000000Change log ========== v2020.02.17 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: several latent bugs in the verifier’s state writing code have been fixed. These only affected large scalar types (> 49-bit) which were not known to be used in any existing real world models (commits 2d27f8b97aa2d24caf217a97a6df7de11e70b1b4, 7bbf8498c42ca8f19a059acc8169be2559b81427, fa87b0a361b1f7dd9fc436c063ffa5a1d4529ee6, 5b4d7154902d8474f6d0233e5af9f3bd85b0a628, 410fdbe533c3597bc2029f63e0426f56250c52bf). * The ``rumur-ast-dump`` utility has been renamed to ``murphi2xml`` to more obviously indicate its purpose (commit d5cb6a6f88498e9d8c999540f66cc838ffe1707a). * When generating a sandboxed verifier (``--sandbox on``), some further time-related system calls are now allowed. This allows the verifier to run correctly on platforms that do not have these system calls implemented in vDSO_ (commits 3ee7d3d3c2f4f35d86b59b6de7139feae8763b4c, 498853681c25272e23cf480c6c8d7269f23a974c). * The verifier’s state reading and writing functions now anticipate that the host platform may be big endian. Full big endian support will require further changes, but this is a first step (commit 8f7bb60c1bc82638dd4ed5f2248c44cd47436461). .. _vDSO: https://en.wikipedia.org/wiki/VDSO Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2020.01.27 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: quantified expressions no longer result in malformed SMT problems in the SMT simplification bridge. This previously prevented some optimisation that could have otherwise occurred (commit 2a1b724d25817b1bf9f95932ed8a4f9bb65a2af9). * Bug fix: pointer compression is no longer incorrectly enabled when targeting the x32 ABI on Linux. This would cause assertion failures or invalid memory references on this platform (commit 37cfa28ad640757eb42d4e394974ad2630987089). * ``forall`` and ``exists`` expressions are now supported by the SMT bridge. The only remaining unsupported expressions are function calls and ``isundefined`` (commits 49a0d0df8d5ea67b1c26b549929f6eea361b879e, 5bb6144f684a905df44aa5955a8d04b37739e65c, 5b4e5e52e4bba0fb7ea03cb63d210701c5f3bc65, 5d4038c3933592b060203bda3e94b259a9ba9f43). * ``rumur-run`` now automatically detects whether your C compiler supports the ``-mcx16`` flag and whether the checker needs to link against libatomic (commits 6547e8b5022522732421ff337ab5113a19afb44a, f7958a3fdad6a280360903108de5f05837fa1e5f). * Some compiler warnings on Linux on ARMEL have been suppressed (commit b56cd94c6af0153dbdb983b8fd4177fc041526c8). Internal changes ~~~~~~~~~~~~~~~~ * ``Model::assumption_count()`` which was previously deprecated has been removed (commit ce2fe9d30db11dbce337355924986af48ee8878d). * ``Symtab::is_global_scope()`` has been deprecated and will be removed in a future release (commit 7943b55ab80e0ecf3563158a2ff7b8100d60ca78). v2020.01.11 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: negative literals no longer cause malformed problems to be sent to the SMT bridge (commit 47f0207dcaee6909d59ddc5577f92b3bf97571b2). Internal changes ~~~~~~~~~~~~~~~~ * Bug fix: a missing header is now shipped (commit 8cf196c3548962b15488abe293b4891740da4da0). v2020.01.07 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: compile errors in the generated verifier on Linux on ARM and RISC-V due to references to missing syscalls in the sandboxing code have been fixed (commit f1af745c54346f74ec650b192e708234de603b58). * Bug fix: the syscalls ``fstat64()`` and ``mmap2()`` are allowed within the verifier’s sandbox on Linux on i386, removing a spurious runtime error (commit 047f23b32e2510af15dd4021a3a63941a909d13f). * The state data structure in the generated verifier is now more aggressively packed, leading to reduced memory usage during checking. The runtime speed of the checker may be slightly degraded. However, see the next item (commits c17f056efcb5d3ef0cbd2160df3762a29ee90530, db0e25f04d9140242643f7c5ebf8b8e9fbc62d82, 3c8ba379e44085e772ada03c8607aac95be2ef30, ae6d776609de0462601f9beb75a8c93ce718f658, 50cff5aef32fa02f096bb7fc161a93f10b829124, 299be2fab2588b3367e8dd3406c8c9c0f591ebc6, 46d495f31c202298aef9f9dcd6638295df3f3e88, c423db32f4c34db11d671d4e9078a4211a237630, c6a040344ef4415e1983bd67dec6bb146b020d5a, f6df17322a787d268c5ba8e587070649533b82c5, a30665fb0b71040c99a19201e37ff9946b77a628, 77b97767661d17bff8b70d42b03ac63ba28c1da6, 654156b1bde6cc8d9dd613053d20de70587827cc, 77c8a12a6d6293de89670d0cbc6c4dc05c6ca9f3, 1b3383e3d2064826f67d211890011d651bfae88d, cff8c6c938cf9b491f136dcb31072d1fe8dcc00c). * ``rumur`` has a new command line option, ``--pack-state``, for controlling the trade off between memory reduction and runtime speed in the generated verifier. See the manpage for more information (commit aca06ba25db9a6a8e6311c8eaec015750371b772). * ``rumur-run`` no longer uses the compiler flags ``-march=native`` and ``-mtune=native`` if they are not supported. This is primarily relevant to non-x86 platforms whose toolchains do not all have these options (commit 1dd341e29dd7033b1d7598af8af899c322880a50). * ``rumur-run`` passes toolchain flags to link against libatomic on architectures that do not have a double-word compare-exchange instruction (MIPS, PowerPC, s390, RISC-V). This causes queue operations that are lock-free on other architectures to take a global mutex, but it seems not easily avoidable (commit 4cd3ffef193e2a87d1dd58a642ebaf93541b70ab). * ``rumur-run`` now uses `Link-Time Optimisation`_ if it is supported (commit 0adcb633ec56b476505e22fa47126437f9665671). * Various minor performance improvements were made to the generated verifier (commits 5af91bf0dfe0d8bef9f7045f5ae5692a179e9ca3, dee407613c0b1fd0c7ab851c6f84cbcb184dbea4, b517be6b83b5c17f97ab82bda448e62ecded9688, fe49bea9af67f71763227e95009441438433522a, fd04cb9c1b3f432cb35f66d6cfe0b0726ad84068). Internal changes ~~~~~~~~~~~~~~~~ * ``validate_model()`` which was previously deprecated has been removed (commit ba3a70ce8902c9baecdc94505f7c71d7dba6dca3). * ``Node::operator==`` and ``Node::operator!=`` have been deprecated and should no longer be called. There is not a consistent notion of AST node equality and these functions only implemented an approximation. They will be removed in a future release (commits 019dbe9c4b2fdf24f8cf16028e73e6105e3336fe, 489947c7e3a01ae256d467565688eded2564f34e). * New functions, ``Expr::is_literal_true()`` and ``Expr::is_literal_false()`` have been introduced for determining if an expression is the literal value ``true`` or ``false``, respectively (commit bd084b982b6f209ec2356bb56f69dc0622b9345b). * A new function, ``TypeExpr::is_boolean()`` has been introduced for determining if a type is the built-in ``Boolean`` (commit f4ad5d02161da0b6f2d5264b6a9db482c392e77e). * Some documentation on the use of C atomic APIs has been added in doc/internals-atomics.rst (commits 85602619752cb8b173a0821bb7afe2a8c301f0e1, 7fb1f0266beafd58e7bf7f859204b0ce61f35b28). * Liveness is now documented as something beyond what is supported by CMurphi in doc/vs-cmurphi.rst (commit 5c82890e2a11ccb5da5e155faba8c7b9c26544d5). .. _`Link-Time Optimisation`: https://en.wikipedia.org/wiki/Interprocedural_optimization#WPO_and_LTO v2019.12.22 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: ``rumur-run`` no longer crashes during UTF-8 decoding in generated C code (commit 7bbd50f6a7241475826e8d380b6a60bb3c6dfd18). * Support for Python 2 in ``rumur-run`` has been dropped. To use this script you will now need at least Python 3.6 (commits 0c4d5f05ebcc937921edd924465827e50d345842, ded15a4d8f23f1f1584566bd6e251679ba8f915c). * The final check of liveness properties now prints regular progress updates (commits ce162be56035e726e1077bb6b6ecc89999e8607e, 2635dae9a4f27962f4ed951a54b3d6c54b9d62c6, 44e80dc6142205904dca188d2a0277b49ed0fb7f, 048a4b54fa7a1c2a7f48fdb8a7e470d396529200, eef60ad5cf61d1a8cac2d1dbcf63581da2590e24). Internal changes ~~~~~~~~~~~~~~~~ * Support for Python 2 in all scripts has been dropped. These now require at least Python 3.6 (commits 5ad77dc6de53de9a78639faba5b65668e43c3ad8, 729a7f8a096369115bde345890bc14e03c5bd428, 6e0d248eae25a8a68b04bb5e99a3172e1e2ab453, 244b41225d36309f9e5985dbe594957782bef7fb). v2019.11.24 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * ``rumur`` has a new command line option ``--smt-bitvectors`` for controlling whether bitvectors are used in preference to integers when interacting with SMT solvers. See the man page for more information (commits 37c84bbe255d3a7aa6d234a8334379edbb24ec3c, 9821bedfa4cdadda8cf1b9f065c07813854ea7d1). * ``rumur`` has a new command line option ``--smt-prelude`` for prepending text to problems sent to SMT solvers. The ``--smt-logic`` command line option is now deprecated and ``--smt-prelude`` should be used to set the logic instead. See the man page for more information (commit ad022eb0767250734562ec1ec932ef4d99ec1f5d). * The ``rumur`` option ``--smt-simplification`` is now automatically enabled if you pass any of the other SMT related command line options (commit 39482d62009232477f18c7e5e295c633004e7b82). * A new tracing feature for memory usage in the generated checker has been added, ``--trace memory_usage``. See the man page for how to use this (commit 4f9195707ae261ed4f6f94d1411579751deff618). * ``rumur-ast-dump`` now has a ``--version`` option to print out its version (commit 76716edc76fbe608a013b0178b6e4d2d72614d08). * Some warnings when compiling generated code with recent versions of Clang have been suppressed (commit 3e9efb2855be52c20023ef3cd03e02b183e22ff5). Internal changes ~~~~~~~~~~~~~~~~ * A new ``version()`` function has been added to librumur for retrieving its version as a string (commits 77ee1c40884627e5418e3c25f902c6d7d73f5f4f, 7f95b7491859548b27ec7d9226d7c28cdec380c0). v2019.11.09 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: returning an expression of range type within a function with a return type of a differing range is now accepted. This pattern was previously rejected by ``rumur`` claiming the types were incompatible (commit 2279e30e74983c8288d097979f31ffecd25b9b4f). * Bug fix: the filename in the AST dump produced by ``rumur-ast-dump`` is now XML-escaped. Previously characters like ``<`` were incorrectly printed as-is (commit cec7f83ac781554a99e9018cef6a0285f67c8955). * ``rumur-ast-dump`` now shows source content in its output even when the input model was supplied on stdin. Previously source content was only included if the input came from an on-disk file (commits ff36e8fec7750a921d4bdc57c509ca7d12fde8cb, 6fbc34e9a6cbee0e8c9f09c9b8dc5796fd3d2aaa, 8fc052d0c3d034ed057ec69aa3ebab95b60234b7). * ``rumur-ast-dump`` now gives the filename in its output as “” when the input model is supplied on stdin instead of omitting it. The ``filename`` attribute of the head ``unit`` tag in the dump has now become mandatory (commit f20463f3e00f5ae2de9871b6b24f83f7799ff4d2). Internal changes ~~~~~~~~~~~~~~~~ * ``rumur::parse()`` now takes its argument as a reference instead of a pointer. The old implementation remains for backwards compatibility but it is deprecated (commit 947ae70c647a955ea6e24b651a6feead64bac787). v2019.10.27 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: several problems with code generation related to statements of the form ``for x := i to j by k ...`` have been fixed. Rumur now supports arbitrary expressions for any of ``i``, ``j``, and ``k``, including reverse (down-counting) loops (commits 1186e622868c124b21637f7ddb5f35f818b18f3b, 8b73384edfceb8c6f55dffdb1ae8d9952b5c8adb, 245887647ac4bfbf08685f97c99c0c84b581e8f8, b7078e9b17fb572ff7126aa42930d3dd50a4577b, df4264e5f72d7e4528211e74444512d58dd32048). * Bug fix: quantified variables are taken into account when calculating range limits for values of simple type (commits e4746dc130d3f69bf623bed503b88b0ba109b176, 3e0ac51a379a2b5612b6d72e3e286955f143e525). * Bug fix: overriding the automatically chosen value type (using ``--value-type ...``) can no longer cause an assertion failure in the generated checker. Forcing a value type that is too small previously violated an assumption in the generated code. This now causes a runtime error (commit 77729447d3cfbb523e3a4a79654eb0a1b5fbd8e8). * Bug fix: the initial pool size of the arena allocator in the generated code was being miscalculated and has now been corrected to approximate 8MB (commit 381f08975e2a0a70cd0a2210a9af12b374580075). * Bug fix: the SMT bridge now correctly detects a failure to start the child process. The check for this was previously incorrect and it would look as if the SMT solver malfunctioned (commit d1cbfd41d3051d548186acf1f17acd85df7f96d8). * Blank (``""``) and unknown logics are now supported by the SMT bridge. Solvers such as Z3 function best when given no ``set-logic`` command (commit 6c92a15f33da3804aaaba628ecc8450ac2fde13d). * The default SMT logic is now ``AUFLIA`` (commit 03ab27d04eccc18c142db7364f7000bf67c12a7f). * Some GCC warnings when compiling generated code have been suppressed (commit bae9b849a781f97e690c8e52196512150aeae4ab). Internal changes ~~~~~~~~~~~~~~~~ * Bug fix: Unresolved ``TypeExprIDs`` with differing names are now considered unequal (commit 7fe656c7db5f2578db826ea1a39a200ece93f57f). * ``TypeExpr::equatable_with`` is deprecated, and replaced by ``TypeExpr::coerces_to`` (commits aa1557bf044e62c8f3adaaca591fe272b30ca19a, e45f214cd2097bbe710a2a3eed9ed196e9feace8, befe6bb4a9b9c342ad3a7a8b96a8bff94c47319d). * ``Quantifier`` has a new member, ``decl``, that is a ``VarDecl`` for the variable it represents (commits c079a460749b1b8e7ea9dd627d369fe3395aa204, 4aba73cb86885531a56228a145ad2529cf5fe2a0). * Quantifier expressions — the bounds of the quantifier — are now validated in ``Quantifier::validate()`` (commit 1b7cd5aad63c8b3e55a266facb8100752946a59d). * The type of a ``TypeExprID`` that refers to a quantified variable is now a persistent, valid ``VarDecl``. Previously it was a synthetic declaration with an invalid ``unique_id`` (commit c567645c4778cbb33d9f696450e9c9c13f12896b). v2019.09.15 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: an alias of a constant is now correctly recognised as constant itself. This makes it possible to, for example, use such an alias as the lower or upper bound of an integer range (commit e4d139880498cfe140ae3298985c615d44f3930d). * The SMT bridge supports variable and type shadowing. For example, if your model has a rule with a local variable with the same name as something in the global state. Such models would previously cause malformed SMT problems to be passed to the solver (commits b2d5c1566530fa009c06b1c2710617b71f7c8c57, 4f5611986b12cbafa9663f1dd7b31f33d3211d25, 7b1718259185ff3e5ceabbb34fca41028da12010). * Smart quotes (“ and ”) can now be used as string delimiters in models (commit 82db1716e7b18259b00ea1941163c4808513793c). * Using an SMT logic without array support (for example, ``--smt-logic QF_LIA``) suppresses SMT simplification in models with arrays. Previously this would cause a malformed problem to be passed to the solver (commit 1100fae5b5c629b2d3e1f7dc386906ae16d7bd5a). Internal changes ~~~~~~~~~~~~~~~~ * Breaking change: ``TypeExprID::referent`` is now a ``TypeDecl`` instead of a ``TypeExpr``. The ``TypeExpr`` that would previously be stored here is available via ``referent->value`` (commit 117ae412d6aa863f54d25fa87106265cced7f680). * A new method ``Function::is_pure`` is available for determining whether a function is side effect free (commits 455acdc883a7080ad764524a7d22e8bf056c9e09, ef5eb689d81bf96c183ad6f74a754eab47229095). v2019.09.07 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * The SMT bridge now supports record types. This makes SMT simplification (``--smt-simplification on``) realistically usable on real world models. Simplification will still give up on some unsupported expressions (commits 787f074328874a470d595576ae9e8b16837582f4, 33d120df8fc7bedf1361a59f328930d311478376, 308a8239eee6dc42684c3bed21210ea95d0dd66e, b9dd7f185d6f22c31d98dfbeb2af4418fb661b79, 13092b8d8c5e62da0178b71825328cc7e75bea5b). * Recursive functions and procedures are now supported. These are supported by CMurphi, Rumur's precursor, but seemingly rarely used in real world models so their absence in Rumur went unnoticed until recently. Mutual recursion is still unsupported (commits e61b8a787ab46bde3c0ce14da885cd3005cc54c9, a9bd211028e591d90e28e2410f5988700bc5efcd). * ``rumur-ast-dump --help`` now shows its manpage instead of abbreviated help text (commits 4198edc67ed37c3dfa91031f90fdfb9e8a5190aa, 8cf86df9ef718d1e22d1ba47a63c9f1a6ba1ad78, 295b565f88660ecf4264ad1ace4e6f88423fab69, 8c612b898e9d42a17847cca3a9435fc575c58135, 577ae2862a45a1d89fe995c1a9bd7bb11fc7e34d, 38a61d670d748d7072162e506c873afa13e757ec). * Function or procedure parameters that shadow a return type are now supported. Previously Rumur would reject such models (commit ff5bbb8cd7a016fbe210757dd1c4b90093c44b4d). E.g.: .. code-block:: murphi type t: 0 .. 1; function foo(t: boolean): t; begin ... * It is now valid to name two rules identically in a model. This can lead to confusing counterexample traces, but sometimes it is natural to name multiple rules the same so supporting this seemed reasonable (commit a1d419c4d70f99d0945164e708ddd90379ddc858). Internal changes ~~~~~~~~~~~~~~~~ * A new interface, ``Function::is_recursive()``, is available for querying whether a function calls itself (commit de4cd48cc2ff64b8ba8eb41163ea45fd1676658c). v2019.08.18 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: Boolean literals (``false`` and ``true``) are now supported by the SMT bridge. These previously led to a malformed SMT problem (commit 0c9917b87523db07b604c566e2f8e3481872857b). * Array types are now supported by the SMT bridge. The bridge is still of limited use as there are many constructs it cannot handle, but it improves incrementally (commits 424467a264b923c53a1b1738604630a05457315c, 5d4f1939ddc5d5d9336f0ce35e953c51e8b5aeca, 5e07b5527a910d12be558d665110a7809838360c). * The default logic for the SMT bridge has been changed to QF_ALIA. As before, this is controllable via the ``--smt-logic`` command line option (commit dc81631881a16764d55dea834ae39d8715b13e83). * Some compiler warnings in the generated verifier have been suppressed (commits e60db38a76b2cd1ce169ad17b442b5285ee83b4c, ef5dd68576dc37d109e2370c653f1a6286042f78, a657bb19ae4ce589e64b217823b0e2c49b8b282e). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2019.07.21 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: quantified ranges that span 0 (e.g. ``-1 .. 1``) now iterate correctly. Previously such loops would become no-ops which could cause the verifier to incorrectly not explore some states. This bug was introduced in v2019.04.28 (commit 2329056db14d87301bba9c56115cdd4539bed1af). * Bug fix: models that contain assume statements but no top level assumptions no longer segfault. This bug was introduced in v2019.05.11 (commits eab626a859982d55b2ebfae8ca216ce79aec25ee, d4ae6d2c88cf0ca5a4e2a4f1f94b375d1405b2a5, ad79600751bb017ff8f85ef34e2747924c0e6eca, 0fd8636f2eca1ed6d90545ab3ee91f4ebae1da85). * Bug fix: the file descriptors used to communicate with the SMT bridge were being configured incorrectly. This caused inconsistent behaviour across different Libc implementations. This bug was introduced in v2019.06.30. Thanks to @wangqr for reporting this (commit 53f20cc00398eefd81a7a1d015517d3051b23548). * The dialect used to communicate with SMT solvers was backported from SMTLIB 2.5 to SMTLIB 2.0. This enables support for more diverse solvers (commit e0e9c5d46c8c2192d6c70987de2a1d50889dc3fd). * There is a new option for specifying the logic in which to encode SMT problems for the external solver, ``--smt-logic``. See the manpage for more information (commits e6b76b518439c0667de0b4b575ec18e5e6994705, 6ba664c341f5796a99a7b4623f424ad4f33c9852, 07ff7f7df1f4e8473f4e5f63dc0654009abb18db). * The SMT bridge learned to understand type-declared ranges/scalarsets, integer constants and enum types. It is still of limited use because it does not understand records or arrays, but support for these will arrive in future (commits c38a0f1188924622e716abbc4dcee924cb10ce52, 33ce2be1adf8c0922ea6fa7594ad9c783df35e20, 7d0146ead2cf30b15ed515beb3c56dd1da8464a8, ca07c576bb272193c1177790c359b5984f636180). * The SMT bridge has increased support for division when using CVC4 (commit e55c4c1b274dfd8797f71f49209d2e0e5eb799d7). * Some inconsistency in the XML output when using ``--output-format machine-readable`` was corrected (commit 22a0c59054563116f6210a886dd538bdfd7cd90a). * Some ``-Wsign-compare`` warnings when compiling the verifier have been suppressed (commits d2949e3516c613f6183ce3219d403e4b3e96add9, 1a7342956115a691118b315bf8ea1cb551f718f9). Internal changes ~~~~~~~~~~~~~~~~ * ``Model::assumption_count()`` has been deprecated (commit 99529844092fcbe1bbbfb3170c7b9a8364a6d055). * ``VarDecl::state_variable`` has been deprecated (commits 39bf6a2661bb6a296fbd73d9f466f052c4865477, 175193b6e0a920f016545008796a99ec3a588bfa, 6a4f9ac363b8c90beac7d5b5ddacc152f5e329d4). * A RelaxNG schema is now included for the XML output of the verifier (commit 123e2507ddf6694ddb7d2bb1baf654e467f28e23). * The validation API has been extended and now also descends into referents. The function ``validate_model()`` has been deprecated (commits 860f71d1db91e71bcab60a8fc8097ad37d3895a0, 499857ec7ab25886be5c4a76802889cb1fc034f8, 5d2449ac780c39cb72f21a03b498c766607fabb7, 45f095c97174b96df5612d0c762283f7187ba0f7). * The data members of referents (e.g. ``ExprID::value``) now have accurate values. This avoids confusion as users can now access these and rely on getting the same, e.g., ``offset`` as the target (commits 7268f636cd9187c30f6bc990abef8e4b493b0534, c3d23559c40b1504bb1a284f76303891fafae23f). v2019.06.30 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: duplicated semi-colons are now ignored. For example, an empty statement no longer causes a syntax error (commit 7e0a3eeff15707e6a67515acd499dce9e598d9ee). * Rumur gained some rudimentary ability to interact with an SMT solver. See the manpage or ``rumur --help`` for information on how to use this functionality. This simplification performed via SMT will incrementally improve in future releases. (commits 45f56b3d06759bd9a0e6343334b5fa2bf2161f2a, 1c75eefb8c9c1b3e1e543cefd992b91066929081, 0f8c1aa01f5ec517d4186ab8f65b81872dcc4374, 9aa75f12adc38efd7a107c90f659ca4d98e8d925, dce3565a8d059e480efd34ff35c5d43134eed607, 4a0b72a25318e642a4648dbcb1082068f7c20354, 4bf443d4a1eb4f069998109f8f4e9380ad35ef6c, c66061ffa216e291a325e3a33cb55fd6d911960b, c32ed61d1b51439e760558712c5c3de5e8cc2a4c). Internal changes ~~~~~~~~~~~~~~~~ * A new member of ``VarDecl`` has been added for determining whether a variable declaration is part of the global state or not (commit 80e6154c748b3cbd36c3b9fb9e1164447e85246f). * ``True`` and ``False`` constants are available to use for comparison or cloning when working with the librumur AST (commit dcb3559fbe03014bdf353649f390fc368b7e813c). v2019.06.12 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: an unlikely edge case was possible wherein the results of checking could be reported inaccurately if one thread was exiting while other threads decided to expand the seen state set. This was never seen in the wild, but has been corrected in this release anyway (commit 8cf9d785c925554e6ec4b2a8a55e619f3ecc66f2). * The generated verifier no longer requires linking against libatomic on i386 platforms. This change means FreeBSD on i386 is now supported (commit 0da98254af604a4812201b8f06dc885dcebb9787). Internal changes ~~~~~~~~~~~~~~~~ * Rumur now compiles correctly on platforms where ``size_t`` is not ``unsigned long``. Thanks to Yuri Victorovich for reporting this (commit 38489a811f0abc4aaaf6f6425dd6321325f959a0). v2019.06.05 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: when generating XML output from the verifier (``--output-format machine-readable``) some text within error messages was not correctly escaped, leading to invalid XML. This has now been corrected (commit ca97a1eb90ac667f3e5f32b41ccbb59940804516). * Bug fix: FreeBSD compatibility which had been accidentally broken was restored. Thanks to Yuri Victorovich for reporting this (commit 43054e83417e028c48b18739f6ac7916cfcbac47). Internal changes ~~~~~~~~~~~~~~~~ * Bug fix: the test suite should now run successfully in a non-UTF-8 locale. As for the above entry, thanks to Yuri Victorovich for reporting this (commits a88c8d2faf2b003e2b65af26cc42b2bcdd82e819, a9e327cd43f94ea22129244f514261ea3880eedb). v2019.06.01 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: the output message for a syntax error on a line containing a tab character previously indicated the wrong column offset with the underlining caret. This has now been corrected (commit 323fda58984e1768b659298afddc5c022160c428). * ``rumur-run`` now exits cleanly and cleans up temporary directories when you terminate it with Ctrl-C (commit 9acb49fd46d8eeddd59104d48621aa1a3c71cd34). * The default load factor of the seen state set has been changed from 65% to 75%. On most models, this decreases the runtime of the verifier. As before, it is still possible to change this value with the ``--set-expand-threshold`` command line option (commit 8ac5bf762d744fc68d8e64918fc7af120b4fc3c7). Internal changes ~~~~~~~~~~~~~~~~ * The documentation available under doc/ has been extended (commits 63e0db1b8d67529e3f042e1b1ed7ffd65ca78cab, 49e8c6a857ba8f9b46d3cf36bb702268d7e822da, f39447766ba43ccf2f218370d6a644024a3e1215, ba0521cfcd2b30d19a125b319ade63775505c73f). v2019.05.11 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: Counterexample traces using "diff" mode (the default) now correctly only show the value of a variable if it has changed compared to the previous state. Previously variables whose values did not change were sometimes repeated (commit 94ef1dec8a82d643dba459d97af3870c9e325528). * Bug fix: Running with counterexample traces disabled (``--counterexample-trace off``) is repaired. Previously this would result in generated code that did not compile (commits f78335f5d72c3fa5b4565103697c678ef62379cf, 58b7ac310caa008d57af71039080095c801956a2). * Bug fix: negative literals are taken into account when determining a type to represent scalars. Previously Rumur would fail to notice that something like ``-1`` in your model implied that values could be negative, and it might have inferred that an unsigned type like ``uint8_t`` was suitable to store this in (commit 2b27e22f00354080589815416b7796d06b37fb6c). * Bug fix: Using ``--max-errors`` with a value greater than ``1`` produces safe code. Previously this would emit a call to ``sigsetjmp`` with live non-volatile local variables. The result could lead to memory corruption or an inaccurate fired rules count, but neither of these were observed in the wild (commit 7dda120345da13f739427915fde630d71bae9ff5). * Bug fix: some spurious ``-Wtype-limits`` and ``-Wtautological-compare`` warnings when building the generated verifier have been suppressed (commit d82f251210560df694f03a6d8b6c5c2cbbe04886). * The concept of disabled properties has been removed. This feature was never documented and had no use yet, so its removal is unlikely to affect any users (commit 4e30098aee291414b5108936548218657fb47900). Internal changes ~~~~~~~~~~~~~~~~ * Some spurious ``-Wsign-compare`` warnings when using older GCC versions have been suppressed (commit 25847dca93e45a3b0616c9f2bd254eae1738f7a1). * The documentation available under doc/ has been extended (commits 5a56d259bf2b9e039ed18a4b48861b48083e730e, 7ab3e74ae2a63809ee657ea981cb2d9ae0da3fb4, b6e8ed7c4c4818aa13d7ec24cc3f7fb40f1d9842, d76467f065585a2cbc5f4f237ea20fb367140c26) v2019.04.28 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: enum types that are printed in error messages now correctly have their members separated by a comma and a space (commit 1107d95909bdd9df019f55f1208c857de5db7239). * Bug fix: one case where the size of the seen set was incorrectly read non-atomically has been fixed. This would only have affected platforms where naturally aligned reads are not already atomic (e.g. not x86). The result would have been a rare chance of a miscalculation of when to expand the seen set. (commit 02d2803ecb6a459a1a41f7d1c630d1b84d6d75ff). * Syntax error messages now provide more information about what token the lexer was expecting to see (commit 06dfee962cb3541fcedf2f319ca4504f90ee0514). * Instead of unconditionally using ``int64_t`` to represent scalar values in the generated verifier, the fastest type that can contain all scalar values in your model is used. You can override automatic selection with the new ``--value-type`` command line argument. This change has no immediate benefit but it opens the way to optimisations using Single Instruction Multiple Data (SIMD) or even SIMD Within A Register (SWAR). (commits 0a5129fb89358ea67ecc32fb07b1d768f655223e, 0933edbb4831c5fc9e483e865b202a6609090b54, f5c8cc54a8a02338a62985aaf2190d7f5fc79ca0, 2fde1dbf0fff5c3776fb77e7468a2e83693a444b, 6d20e571685f18cdb2d9bf6dd77c615ce1ab5385, e98a3d0041d64dd331a16e45897e9c3a789e0235, f9a29ea64cccbc41155b689d80ea6eb3be9189e9, c95df7007b48a89df981eec037679dd3cb87dab5, 5b33f977a55a4bd370aefea205548b1b0bf887d9, 5e5945535ff60ed01501d2b10282220b96b009bc, 5e1ee6dbe6e784516a1171996bb442e9936e426b, 79579fd5ee7cc3c120439b5d3187a09ffd5dcd6e). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2019.04.13 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: malicious models can no longer cause bad printf format strings to be generated (commits 6b30d43f6672278db0c0d7c8dfd5dbe83785fad5, a27c2391ede24c0833b045d0d4a138ecb829434b, 322d1e26b343cdc308efb50ce2d952bb26ad9ad8). * Bug fix: characters in text like rule names are no longer dropped when using XML output (commit f119f745218ed9404f6922e95aa6076bc0bdf291). * Bug fix: unnamed invariants are now correctly numbered when they are referred to (commit 450a2e7b9ced7f670eaf568e9ba484ea43a2dcb4). * Bug fix: with deadlock detection enabled (default) and multiple errors (``--max-errors ...`` with a value > 1), deadlock counterexamples are no longer duplicated in the error output (commit 17ebb307b68cb323ad0840903b96070ea1b6ca0a). * New syntax has been added for writing liveness properties. See doc/properties.rst for how to use this (commits e99fa1104ff578106075f6dc19c35b4ef2f7d986, ee1aecd172edb9fa5be775548841e38c4aa547b0, 36fae15066562eedee594fa1fd77e60af19e13bd, 4c6ee24bc922955f419c05391fa1ddc49cbc122e, 53f80d8565af4217bfe11ac2bfe549d9b2ada0af, b094269cfe516bad7bd3ab0993288ff7f3a8285a, 6ed296f61b7b942323974a7d40c2b20f7003ff26, ac54ed1cef5326260128d189a3705679a3ba02aa, 85cbc94ac9b734572874d3564d9a4240f10614f9). * Support for macOS has been extended back to XCode 7.3 (commit 35e1803b370f8a47df84812eab19bbb01dcf4e41). Internal changes ~~~~~~~~~~~~~~~~ * The test case tweak snippets (``-- ...`` Python comments at the beginning of test cases) can now refer to whether XML output is in use or not (commit af393a106773c98b79f283f02e250ec9ca9a73a5). * Using the ``-- checker_output: ...`` test case tweak no longer limits a test case to running when XML output is not in use (commit af393a106773c98b79f283f02e250ec9ca9a73a5). * There is a new API function for counting the liveness properties in a model (commit ee1aecd172edb9fa5be775548841e38c4aa547b0). * The build dependency on ``xxd`` (bundled with Vim) has been removed (commits a8575179f9a5c956be5bb50c182bbb89f1d8d057, 6b907684c4d7696acf6f9ea2a2ca566e5175da18, 43759055bf873814ec18cb692ee9a6d9d6889d1a). v2019.03.30 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: an error when compiling the generated verifier on non-x86-64 was addressed (commit 7e59f1c25a71fd6c3444fc11adc6f932b32ce926). * Bug fix: the Vim syntax extensions were missing the ``property`` keyword which has now been added (commit 9e70f6114899ca04556c3cdeb198928a65ab19fc). * Errors when generating the verifier are now printed showing the relevant source line from the model. They are also colourised Clang-style when possible (commit e7f2b615cb432bf8fab55d3a00225f3b26e8d8d9). * Support for sandboxing the generated verifier has been extended from Linux and macOS to include FreeBSD (using Capsicum) and OpenBSD (using ``pledge``) (commits b73b180dd7fedb2795f19e8a065eefe429f1177e, cb53074aaa1c898c6c0a3d6e962597b9c77c3785). * Expansion of the set of seen states has been optimised, resulting in a ~4% decrease in the runtime of the verifier. This change reduced contention, so likely leads to greater speed ups on large, multicore platforms (commits 022c3708b24b828a96f3a50c0f11c7cc1476a439, 5f4bb2cd96660a48518680f992fee041566ac722, 2e84387ec6f56c42f41ea21e17ba99eef501ab65, 5b29f2c4cb96989ba862a19acfcae0912a19f86c, 9287f5af063a430e83c8957d9f7282d1af33d6ba). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2019.03.21 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * A new bounded model checking mode is available using the command line option ``--bound``. See the manpage or ``--help`` for more detailed information (commit e60697531ab636d374946d547ae65cd380b2ce0b). * The names of quantifier variables are now included in the XML produced by ``rumur-ast-dump`` (commit 78539fa086bbdaf06c5a079e5e482637cf6f2e11). * Some optimisation has been done to state handles, resulting in a ~9% decrease in the runtime of the generated verifier (commits d783655eae837b805b69185d1d198ea142825973, 96268246ad3c9635998647fb31faf73e6721c83b). * Support for GCC on Linux has been extended from 4.8 back to GCC 4.7. It is unlikely Rumur will ever support a lesser GCC version than this (commit 76a97b5354cc10cbd5fd188c385eeb457b3fd2ab). * All major BSD flavours (DragonFly, FreeBSD, NetBSD, OpenBSD) are supported. Rumur now runs on all major desktop operating systems except Windows (commits 6524f1eaedc6724fb26462ec901c241ded7861e1, 026c9a476ba5efea5dd4fd7a5a8bcec7588381e8, 7e9addb34df01abe7449823c33772985e9f6172b). Internal changes ~~~~~~~~~~~~~~~~ * Bug fix: a memory leak on passing invalid command line options has been removed. This is under "Internal changes" because the leak occurred immediately prior to program exit, so would only have affected users debugging or embedding Rumur (commit 4f89903e244c7c188577d082c204bdb344ed1af8). * New options for scoping the range of tests that the test suite runs. This is mainly for use by the continuous integration setup (commit ba2377a3b7240774d6bfb6745bb3c424c67b9277). v2019.03.11 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: enums and booleans that were used as ruleset parameters would previously have their values printed numerically in counterexample traces. For example, ``false`` would be printed as ``0``. Both are now printed as their textual names (commit 40c281d80342e684401425769e8e91ec78e3b019). * Support for "cover" properties has been introduced. These are described in doc/properties.rst (commits 22a865897d23e2281541fe43276277b4b980a14d, 29ac671ca93a0eef79b4f2b85a43da624d10938f, f9fe9614a4beb930f54db50250e4004ad773cee5, b4c5ead18eb3d99d2434aad6732cfce305c629c2). * State allocation has been optimised, resulting in a speed up of ~46% and peak memory usage reduction of ~9% in the generated verifier (commit 7ddf00bbce10a5f0cdd994658ac4545b186826ac). * When using GCC, the minimum required version has been reduced from 4.9 to 4.8 (commits c84bad26079f49a40b4c9cbdcd50b508292a8689, 657eea8b8b84d269916207268edab85d71aba532, ff5a32521e4f937bd4d81b3ac7ae7204c8f913ec, 227f340a059ce704ac1dff9cff75d721b987e147, 7ba30edd5657c94fe5fe8c559fbde179817c795b, 554d37e47cc9f878f65161d3ae51f6fbb9345bd8, 3c827ae7b0f20d3f3f10118f61adcf73e58ee701, e929000525239eb357ad780c95aa54008633c678, a1ece0ad453ef95decd6256dac69b2af99ced2ff, b18e0430c8cd1cb5f67827e8ca2a6b0ab4117147, 4e04bb5a6333df60444710f949486ea34739acc0). * A Vim extension is included in misc/murphi.vim to add support for syntax highlighting Rumur's Murphi extensions (commit 6dbcd208025a4a07b94d818110613a69efc05e4a). Internal changes ~~~~~~~~~~~~~~~~ * Bug fix: the test suite no longer attempts to output decoded UTF-8 data on stdout/stderr (commit 551d18398189cb11ba6274d708d3ff293af034c7). v2019.03.02 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: enum types with duplicate members are now rejected. Previously, members would silently shadow earlier duplicate members (commit b476ffbdb7f5afb245c933a89d8f3cf9ecc8a884). * Bug fix: models that redeclare symbols are now rejected. Previously, definitions would silently shadow earlier same-named definitions (commit 96b8acab16310f4e80008b92827f804ba6e3ae66). * The generated verifier produces more context information in error messages (commits 45a63a9f26f531587d0c461da74467e2cc008c38, 7238dcacbf676c2649cfe82c98df25dbe96af93d, 9384c756477cbf164ea7f41227b053fca4c67fc5, 063e92bd53a5dbbb642e1d5c302a9240afff5fbc, 668c1d6ab02e9c55cfd8119e5a403c5595cd5b45, 39d35f4344633c2e1280fc0d5b28e2356140229b, 434fbf2f50d69b7824a224280bd5f7f3bcc2275d, 6822bba8a280b70d53d6dbb470f631143df0b5c4). * The implementation of the queue of pending states has been further optimised, resulting in a ~25% reduction in the runtime of the generated verifier (commits 8f0329c33343cfcf16675a110ed3211b9abc95e3, 2153f1f9e0ac7e2d015aff58cd0d8007901de808). * The warning emitted by Rumur when your model is missing a start state is now suppressed when you pass ``--quiet`` (commit 55514d39e40b2c018379e15d2f706e0a1c56ed18). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2019.02.14 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: calls of procedures (a.k.a. functions with no return type) are now rejected when appearing within an expression (commit 72d9196308a8b0d3b43929566beb571029b7e006). * Bug fix: unary negation that never worked correctly has been repaired (commit 48228f32c43423cd956f988fb0567fca080b9b28). * Between v2019.02.01 and v2019.02.04, there was an unintended performance regression in the runtime of the generated verifier (commit f5589751de2f860c3cca7d681f9710160d3c20a8). This has been addressed and the verifier runs faster than even v2019.02.01 (commit ccf410672326e04230331576a1c76003ad2ab1a3). * Returning a range-typed expression within a function that returns a *different* range type is now supported (commit e196ed43199d6d47d36eb9f225017c2123e294c3). Internal changes ~~~~~~~~~~~~~~~~ * ``Expr::type()`` returns a smart pointer that is never null (commits d89de1376abe5bbbef61d68b02c45a35c4f9a12f, beeffb42ad6514448e463e8a2d73d3a1d8b35898, e196ed43199d6d47d36eb9f225017c2123e294c3, 5dcf10f2821ffb8a2080b297fc664485884747be). v2019.02.04 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: using a non-scalar (record or array) result of a function call as an input parameter to another function or procedure would previously cause an assertion failure during code generation. This has been addressed and correct code is now generated (commit 73dcbf237f747d8958528127f6a05442bd3bf2c0). * Bug fix: the convenience wrapper ``rumur-run`` now correctly exits when one of its steps fails and also returns the correct exit status (commits 9eae5c5a22a87507713a2ebc5b57120de00e6f10, 46cc017ee8c6337453601c245e6e764254687f48, 235fbc552addefc1f34e8840a9d80845b423d30e, 80825dfb406eb6f39aaa01c9011eadd7b6ad9b05). * Bug fix: column offset information in the XML produced by ``rumur-ast-dump`` was sometimes off by one. This is now corrected (commit 7d8dc868d9e1c31243b15e3de116e4f0740a38b3). * GCC 4.9 is now supported. Previously the oldest version of GCC we supported was 5 (commits 83ce80ad8bba3f48d4316dba29b4795c13facd03, 0ed86df81586b5808be82c924ad964b25cb38447). * The error message when a model assertion fails has been made more informative (commit 608fe69abfd7aa7ab724a42b1327bb055f7fb3ac). Internal changes ~~~~~~~~~~~~~~~~ * Nothing relevant. v2019.02.01 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * The values of ruleset parameters are reported in counter-example traces (commits 37f742797d8c76523607f90e80a5d1cc0ff16226, f7a8b012bfce555f156d1682cfd1073e8ccfe462, ee2d85200708cc70c2df056409d3da1283da2218). * The name of a failing invariant is given in the failure message (commit 60e864ccd8abefd617f21af4e1a78c53d1a3a66e). * Comparison of complex types using ``=`` or ``!=`` is supported in models (commits 107f6c4ac88ce4e2c6745507aa332aa17dfd3264, bbd3beebb6ce0a51475a241eff45d7c2a223bcbb). * ``rumur-run`` passes ``-march=native -mtune=native`` to the C compiler (commit ad9e26bfafb1cdf3877f46dd31b4072e1efffb5d). * Rulesets with non-constant parameters are rejected (commit 90810e214e7fa200d683f4ee4b79ef489d9e3d34). Internal changes ~~~~~~~~~~~~~~~~ * Various new interfaces were added to types and quantifiers (commits 6ea740ec2f6518733a626805af6b0f7275fc9b86, 41e01629c30293dc91dd460d0286b74763eba387, aea30d24234777a0b0698c1ce6f28f8267b15d9f, 154885bac4950b70c80620566e37d5a2890d317e). v2019.01.12 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: an issue that could have led to the corruption of reference-counted pointers in the checker was addressed (commit 04fede03a59624f3c08ee7b80d8f928dfc1e45be). * The licence has been changed from the vague "public domain" to the Unlicense. This is just a clarification and does not indicate a change in the licensing intent (commit 592e0c62ff9b1b7bf1bada4e41fa058d2d669ab8). * All Python components now work with Python 2 in addition to Python 3 (commits f04b1442af0b30581b17fc517aeecce99bd8f1ef, de4fcd64ed20b128e7dceb44dd57b757e15096c5). * ``rumur-run`` and ``rumur-ast-dump`` now have accompanying manpages (commits fe484a28ac3f77766b7de30569c85350b499ffbd, 3c2ba659f36e6b4cbedb8fd35b7f5c0f0af3be65). * A Zsh completion script was added (commit aac9e7718f3849b66932e375d673ea6b80547ff8). * Missing documentation for the ``--output`` option was added (commit 3047fb45f4a1aee9c5064ee9bb260df25bf72c8e). Internal changes ~~~~~~~~~~~~~~~~ * A RelaxNG schema was added for the format produced by the AST dumper (commit 36d26f6c327dbbd541537ad12d07636aba55f502). * Rumur should now be compilable with ``-pedantic`` in most environments (commits b4ef8c0e8bcc1af2a1afd00204e2df735928488f, 526afa1fb9e00bb159caf8ce49f83e40c571f747). v2018.12.20 ----------- User-facing changes ~~~~~~~~~~~~~~~~~~~ * Bug fix: boolean constants are now usable in boolean expressions, rather than being considered ranges (commits 3f8e25eed1b2cd88b04aec973b84efea3737f16b, 6ee751955a0781becae7dcc0e34a7477e668e462). * Bug fix: indexing a non-array expression is now reported as an error, rather than causing an assertion failure (commits 606657b7fc656fd4c304523b98c5e2828a896271, a31c9973f63a719b676be97e7a893dd21d451511, 5222f6ddce51ea66ceda6ecb0e016a94308e835b). * Bug fix: calling a function with incorrect arguments is now reported as an error, rather than causing an assertion failure or uncaught exception (commits 705793e6b0f3646d30dcab247d27cdd3ac94430c, 2427b74c4d6fb40115943dc01bbd66cc4ada7d17, fe9344f5b723608cd8916bd16c2688f9494ca92a). * Bug fix: trying to access the field of a non-record expression is now reported as an error, rather than causing an assertion failure or uncaught exception (commits f72373b30e8031baa8c8e0e953c05e47874ae854, 76d09b6bf77414b51af2bf1da0ecd099c25ad2e1, 27b61a5f6b0be2e838a39c02e567c87b4ce80d76, b917ece31a209ba9586c7c44577ba34b19a2c0a7). * Bug fix: The boolean literals ``true`` and ``false`` are now accepted in all possible casings (commits 121d724c00e2afc1d1fa6c525dad958646936fb1, 68e9164ae8a5a17c6e6346266051b24780bbf203). * The ``isundefined`` operator is now implemented (commits d12841246e207a5691159f8ed46faf08cb596dd5, 8e3563a0309d57dc19dbd7f0d1c50a8f30878559). * Range-typed expressions can now be passed in to functions as non-var parameters of a differing range type, where previously this scenario would only accept rvalues or identical range types (commits 343e97eeeb8ccd4c59bf150c42c0b74f1b00ec6a, 09cfec88a1e648eaa240404c2b215ed4cefec926, 2324e3efc370a09a289a4998c677cf1bfb31a245, 90a95c31d5c04a6083f753bc15f566658abcdf9d). * If the generated verifier is multithreaded, it now prints a thread identifier in each progress line (commit b222b3bc5fad2ff6e8371d3b46ad28809daa2451). * Some spurious compiler warnings when building the generated verifier have been suppressed (commits 8a05ab0d209c0b8cbfa7048d5775505c1f70f283, 4f447fdcc44f694f8bc1d948bbc17d690ca3d59f, 7885b611ef9d9e6d18629b1eb696def0183eed16). Internal changes ~~~~~~~~~~~~~~~~ * The use of ``static_assert`` has been replaced with ``_Static_assert`` (commit ad26fe525f7ba99dfbf3d5c6bc248ef41602d9a5). * ``Expr`` and ``Decl`` gained a new ``is_readonly`` method (commit 47c27f217b035fa9881fe32576354c08669b0899) and the distinction between the concepts of "lvalue" and "writable" is now more accurate. * The test suite has been backported so it also runs on Python 2 in addition to Python 3 (commit 7fe028271d376188d8b5d6353e0bca720d12e6b9). v2018.12.08 ----------- * Hello world! rumur-2020.02.17/CMakeLists.txt000066400000000000000000000030311362265074000160210ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1) project(rumur LANGUAGES CXX) # This seems to be some magic to get libraries to install correctly. include(GNUInstallDirs) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -W -Wall -Wextra -Wwrite-strings -Wmissing-declarations -Wshadow") # enable even more warnings if the compiler supports them include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG(-Wcast-qual HAS_WARNING_CAST_QUAL) if(HAS_WARNING_CAST_QUAL) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wcast-qual") endif() CHECK_CXX_COMPILER_FLAG(-Wcast-align HAS_WARNING_CAST_ALIGN) if(HAS_WARNING_CAST_ALIGN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wcast-align") endif() CHECK_CXX_COMPILER_FLAG(-Wstrict-aliasing=1 HAS_WARNING_STRICT_ALIASING_1) if(HAS_WARNING_STRICT_ALIASING_1) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wstrict-aliasing=1") endif() CHECK_CXX_COMPILER_FLAG(-Wpointer-arith HAS_WARNING_POINTER_ARITH) if(HAS_WARNING_POINTER_ARITH) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpointer-arith") endif() # Enable --as-needed, present on GNU ld on Linux, to minimise dependencies. if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--as-needed") endif() add_subdirectory(murphi2xml) add_subdirectory(librumur) add_subdirectory(rumur) enable_testing() add_test(tests env PATH=${CMAKE_CURRENT_BINARY_DIR}/rumur:${CMAKE_CURRENT_BINARY_DIR}/murphi2xml:$ENV{PATH} ${CMAKE_CURRENT_SOURCE_DIR}/tests/run-tests.py --jobs 1) rumur-2020.02.17/CONTRIBUTING.rst000066400000000000000000000013421362265074000157250ustar00rootroot00000000000000Contributing ============ Patches are welcome in whatever form. Contributions will be distributed with Rumur under a public domain licence. Providing a change to be included implies that you agree with this and your contribution does not cause trouble with trademarks or patents. There is no CLA to sign. Issues are tracked on Github, but it is not a requirement to report issues there. Emailing the author is also fine. Feel free to report even the smallest problem, also typos in the documentation. If the above text sounds familiar, it is modelled after the excellent `Vim contribution guidelines`_. Basically, be a good open source citizen. .. _`Vim contribution guidelines`: https://github.com/vim/vim/blob/master/CONTRIBUTING.md rumur-2020.02.17/LICENSE000066400000000000000000000022721362265074000142740ustar00rootroot00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to rumur-2020.02.17/NEWS000077700000000000000000000000001362265074000157772CHANGELOG.rstustar00rootroot00000000000000rumur-2020.02.17/README.rst000066400000000000000000000046561362265074000147660ustar00rootroot00000000000000Rumur ===== Rumur is a `model checker`_, a formal verification tool for proving safety and security properties of systems represented as state machines. It is based on a previous tool, CMurphi_, and intended to be close to a drop-in replacement. Rumur takes the same input format as CMurphi, the Murphi modelling language, with some extensions and generates a C program that implements a verifier. Quickstart ---------- Installation on Debian Unstable ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: sh apt install rumur Installation on FreeBSD ~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: sh pkg install rumur Thanks to `yuri@FreeBSD`_ for packaging. .. _`yuri@FreeBSD`: https://github.com/yurivict Building from Source ~~~~~~~~~~~~~~~~~~~~ First you will need to have the following dependencies installed: * Either GCC_ or Clang_ * Bison_ * CMake_ * Flex_ * Libgmp_ * Python_ ≥ 3.6 Then: .. code-block:: sh # Download Rumur git clone https://github.com/Smattr/rumur cd rumur # Configure and compile mkdir build cd build cmake .. make make install # Generate a checker rumur my-model.m --output my-model.c # Compile the checker (also pass -mcx16 if using GCC on x86-64) cc -std=c11 -O3 my-model.c -lpthread # Run the checker ./a.out Compilation produces several artefacts including the `rumur` binary itself: * rumur: Tool for translating a Murphi model into a program that implements a checker; * murphi2xml: Tool for emitting an XML representation of a Murphi model’s Abstract Syntax Tree; * librumur.a: A library for building your own Murphi model tools; and * include/rumur/: The API for the above library. Comparison with CMurphi ----------------------- If you are migrating from CMurphi, you can read a comparison between the two model checkers at `doc/vs-cmurphi.rst`_. .. _doc/vs-cmurphi.rst: doc/vs-cmurphi.rst Legal ----- Everything in this repository is in the public domain, under the terms of `the Unlicense`_. For the full text, see LICENSE_. .. _Bison: https://www.gnu.org/software/bison/ .. _CMake: https://cmake.org/ .. _CMurphi: http://mclab.di.uniroma1.it/site/index.php/software/18-cmurphi .. _Clang: https://clang.llvm.org/ .. _Flex: https://github.com/westes/flex .. _GCC: https://gcc.gnu.org/ .. _Libgmp: https://gmplib.org/ .. _LICENSE: ./LICENSE .. _`model checker`: https://en.wikipedia.org/wiki/Model_checking .. _Python: https://www.python.org/ .. _`the Unlicense`: http://unlicense.org/ rumur-2020.02.17/common/000077500000000000000000000000001362265074000145545ustar00rootroot00000000000000rumur-2020.02.17/common/help.cc000066400000000000000000000033041362265074000160130ustar00rootroot00000000000000#include #include #include #include "help.h" #include #include #include /* The approach we take below is writing the manpage to a temporary location and * then asking man to display it. It would be nice to avoid the temporary file * and just pipe the manpage to man on stdin. However, man on macOS does not * seem to support reading from pipes. Since we need a work around for at least * macOS, we just do it uniformly through a temporary file for all platforms. */ int help(const unsigned char *manpage, size_t manpage_len) { int ret = 0; // Find temporary storage space std::string tmp; const char *TMPDIR = getenv("TMPDIR"); if (TMPDIR == nullptr) { tmp = "/tmp"; } else { tmp = TMPDIR; } // Create a temporary file size_t size = tmp.size() + sizeof("/temp.XXXXXX"); char *path = new char[size]; snprintf(path, size, "%s/temp.XXXXXX", tmp.c_str()); int fd = mkstemp(path); if (fd == -1) { ret = errno; std::cerr << "failed to create temporary file\n"; goto done; } // Write the manpage to the temporary file { ssize_t r = write(fd, manpage, manpage_len); if (r < 0 || (size_t)r != manpage_len) { ret = errno; std::cerr << "failed to write manpage to temporary file\n"; goto done; } } // Close our file handle and mark it invalid close(fd); fd = -1; // Run man to display the help text { std::string args("man "); #ifdef __linux__ args += "--local-file "; #endif args += path; ret = system(args.c_str()); } // Cleanup done: if (fd >= 0) close(fd); if (access(path, F_OK) == 0) (void)unlink(path); delete[] path; return ret; } rumur-2020.02.17/common/help.h000066400000000000000000000001721362265074000156550ustar00rootroot00000000000000#pragma once #include // Display help information int help(const unsigned char *manpage, size_t manpage_len); rumur-2020.02.17/doc/000077500000000000000000000000001362265074000140315ustar00rootroot00000000000000rumur-2020.02.17/doc/internals-atomics.rst000066400000000000000000000135611362265074000202250ustar00rootroot00000000000000Internals — Atomics =================== When writing multithreaded C code, there are four commonly used APIs for atomic operations: 1. `inline assembly`_; 2. `__sync built-ins`_; 3. `__atomic built-ins`_; or 4. `C11 atomics`_ .. _`inline assembly`: https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html .. _`__sync built-ins`: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html .. _`__atomic built-ins`: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html#g_t_005f_005fatomic-Builtins .. _`C11 atomics`: https://en.cppreference.com/w/c/atomic Rumur needs to use one of the above for each of its atomic operations. Inline assembly is essentially a non-starter. It is non-portable and awkward. Of the remaining three, they do not offer the equivalent functionality as seen from the table below. +----------------------------------+---------------------------------+------------------------------------+ | __sync built-ins | __atomic built-ins | C11 atomics | +==================================+=================================+====================================+ | | ``__atomic_load_n`` | ``atomic_load`` | | | ``__atomic_load`` | | +----------------------------------+---------------------------------+------------------------------------+ | | ``__atomic_store_n`` | ``atomic_store`` | | | ``__atomic_store`` | | +----------------------------------+---------------------------------+------------------------------------+ | | ``__atomic_exchange_n`` | ``atomic_exchange`` | | | ``__atomic_exchange`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_bool_compare_and_swap`` | ``__atomic_compare_exchange_n`` | ``atomic_compare_exchange_strong`` | | ``__sync_val_compare_and_swap`` | ``__atomic_compare_exchange`` | ``atomic_compare_exchange_weak`` | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_lock_test_and_set`` | ``__atomic_test_and_set`` | ``atomic_flag_test_and_set`` | | ``__sync_lock_release`` | ``__atomic_clear`` | ``atomic_flag_clear`` | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_add`` | ``__atomic_fetch_add`` | ``atomic_fetch_add`` | | ``__sync_add_and_fetch`` | ``__atomic_add_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_sub`` | ``__atomic_fetch_sub`` | ``atomic_fetch_sub`` | | ``__sync_sub_and_fetch`` | ``__atomic_sub_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_or`` | ``__atomic_fetch_or`` | ``atomic_fetch_or`` | | ``__sync_or_and_fetch`` | ``__atomic_or_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_and`` | ``__atomic_fetch_and`` | ``atomic_fetch_and`` | | ``__sync_and_and_fetch`` | ``__atomic_and_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_xor`` | ``__atomic_fetch_xor`` | ``atomic_fetch_xor`` | | ``__sync_xor_and_fetch`` | ``__atomic_xor_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ | ``__sync_fetch_and_nand`` | ``__atomic_fetch_nand`` | | | ``__sync_nand_and_fetch`` | ``__atomic_nand_fetch`` | | +----------------------------------+---------------------------------+------------------------------------+ Some relevant points to note from this table and from other sources: * The __sync built-ins have no way of expressing that a load or store must be atomic. This is not an issue on platforms like x86 where all naturally aligned loads and stores are guaranteed atomic, but it is a problem for other platforms. * The __sync built-ins have no way of expressing an atomic exchange operation. * The __sync built-ins have no way of expressing memory ordering. This is not an issue on platforms like x86 that guarantee sequential consistency, but on platforms that allow weaker guarantees it is an issue. * Some of the C11 atomics expect to operate on ``atomic_`` types and are not directly usable on regular variables. * Double-word compare and exchange using either the __atomic built-ins or the C11 atomics causes a call to libatomic to be emitted. This is functionally correct but impairs our ability to use these in lock-free algorithms. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878 for more information. * The C11 atomics are not available prior to GCC 4.9. With these constraints in mind, the checker that Rumur generates mostly uses __atomic built-ins, resorting to the __sync built-ins when doing something that is not possible with the __atomic built-ins. rumur-2020.02.17/doc/internals-hash-function.rst000066400000000000000000000012421362265074000213250ustar00rootroot00000000000000Internals — Hash Function ========================= The seen state set (described in `internals-seen-state-set.rst`_) hashes the contents of state data to determine an index at which to store that state. The hash function used for this is MurmurHash_ from `Austin Appleby`_. This hash function was chosen because it has reasonable statistical properties and is public domain. There might be a performance benefit to switching to a different hash function, but so far this has not proved a bottleneck. .. _`Austin Appleby`: https://github.com/aappleby .. _`internals-seen-state-set.rst`: ./internals-seen-state-set.rst .. _MurmurHash: https://github.com/aappleby/smhasher rumur-2020.02.17/doc/internals-reference-counted-pointers.rst000066400000000000000000000063101362265074000240160ustar00rootroot00000000000000Internals — Reference Counted Pointers ====================================== The generated verifier contains an implementation of reference counted pointers, ``refcounted_ptr``. This document describes their design and how they work. Definition ---------- .. code-block:: c struct refcounted_ptr { void *ptr; size_t count; }; A reference counted pointer contains two fields, the raw pointer and a count of the number of outstanding (i.e. live) references. You can think of the count as the number of threads who have "borrowed" a copy of this pointer. Interface --------- The basic operations on a reference counted pointer are get and put: .. code-block:: c void *refcounted_ptr_get(struct refcounted_ptr *p); size_t refcounted_ptr_put(struct refcounted_ptr *p, void *ptr); The get operation takes a reference to the pointer (increments its ``count`` by ``1``) and returns the raw pointer value. The caller can freely use this pointer as it would any other pointer. The put operation returns a reference to the pointer (decrements its ``count`` by ``1``) and returns the number of remaining references to this pointer. If the returned value is ``0``, the caller can free the raw pointer as it knows there are no other remaining users of it. There is nothing to prevent another thread calling ``refcounted_ptr_get`` on a pointer whose reference count has just dropped to ``0`` and thereby increasing it back to ``1``. That is, after calling ``refcounted_ptr_put`` and receiving ``0`` as the return value, it is possible for another thread to increase the reference count prior to the pointer being freed. It is up to threads to coordinate their actions to avoid this race. The ``ptr`` parameter to ``refcounted_ptr_put`` is for debugging purposes only. Two other operations are provided for setting the value of a reference counted pointer: .. code-block:: c void refcounted_ptr_set(struct refcounted_ptr *p, void *ptr); void refcounted_ptr_shift(struct refcounted_ptr *current, struct refcounted_ptr *next); The first of these, ``refcounted_ptr_set``, sets the pointer's raw value to ``ptr`` and zeroes its reference count. The second, ``refcounted_ptr_shift``, replaces fields in ``current`` with those in ``next`` and zeroes ``next``. Neither of these functions use atomic operations and threads are expected to coordinate with each other such that calls to these do not race with each other or with gets and puts. Implementation of Atomic Updates -------------------------------- Both ``refcounted_ptr_get`` and ``refcounted_ptr_put`` need to operate atomically on the pointer they are affecting. This is straightforward for put that only needs to atomically decrement the reference count, but get needs to read the pointer and the count atomically while incrementing the count. To achieve this, we use a double-word compare-exchange operation. First we read the entire structure atomically. We then extract the pointer value and increment the count in the local copy we have. Finally we use an atomic compare-exchange to write the changes back to the original structure. This relies on platform support for atomic double-word compare-exchange. For example, on x86-64 this involves a ``CMPXCHG16B`` instruction. rumur-2020.02.17/doc/internals-seen-state-set.rst000066400000000000000000000135531362265074000214300ustar00rootroot00000000000000Internals — Seen State Set ========================== When exploring the state space of your model, the generated verifier stores the states it has encountered in a global set structure. This structure's key properties are: * Insert-only: there is no support for removing elements from the set. * Thread-safe, lock-free: multiple threads can be inserting into the set at once without creating a scalability bottleneck. * Dynamically expanding: when the set fills up, its capacity is expanded to hold more states. High Level Design ----------------- The seen state set is modelled closely after the data structure described in Maier et al, "Concurrent Hash Tables: Fast and General(?)!" in arXiv 2016. This paper lays the ground work for how to build a scalable concurrent set, and the remainder of the present document will assume you have read and understood the paper. The main point of deviation from Maier et al's design is that we do not implement the optimisation that allows their set expansion to use non-atomic writes. In the Rumur design, each thread has a unique "chunk" (a 4KB block of state pointers) that it is migrating, but the destinations for these elements may collide with the writes of another thread. We use atomic exchanges to guard against this interference. The main reason we don't implement this optimisation is that I was initially anticipating a configuration where you fully fill the seen set before expansion, instead of expanding at a threshold like 60% occupancy. Now I see this as a less realistic scenario, so it might be worth looking into this optimisation in future. Definition ---------- The set itself is quite simple: .. code-block:: c struct set { slot_t *bucket; size_t size_exponent; }; The total capacity of the set can be computed by ``1 << size_exponent``, encapsulated in the function ``set_size()``. We store an exponent rather than the size itself as a micro-optimisation to make it explicit to the compiler that the set capacity is always a power of two. The occupancy of the set (how many elements are actually stored in the set) is stored in a global: .. code-block:: c static size_t seen_count; The occupancy only grows throughout the program's lifetime and storing it in a global (a) allows it to be referenced without a pointer indirection and (b) decreases the size of reallocation by ``sizeof(size_t)`` when expanding the set. Local and Global Pointers ------------------------- During execution there is a single global pointer to the currently active seen set and a local pointer to this set for each thread. The global pointer is reference counted (see `internals-reference-counted-pointers.rst`_). The motivation for this and mechanics of pointer borrowing and returning will become clearer when we discuss set expansion. .. _`internals-reference-counted-pointers.rst`: ./internals-reference-counted-pointers.rst Set Insertion ------------- The common path for inserting an element into the set is quite straightforward. A thread calls the set insertion function: .. code-block:: c bool set_insert(struct state *s, size_t *count); Within the function, a hash function (described in `internals-hash-function.rst`_) is used to compute the index at which the element should be stored. Using a thread-local pointer to access the currently active seen state set, the insertion algorithm uses `linear probing`_ to find an empty slot to insert the state pointer into. .. _`internals-hash-function.rst`: ./internals-hash-function.rst .. _`linear probing`: https://en.wikipedia.org/wiki/Linear_probing If you are following along in `../rumur/resources/header.c`_, you will have noticed two odd things: 1. The insertion algorithm checks occupancy and conditionally calls ``set_expand()``; and 2. There is a check for something called a "tombstone." Both of these are explained in the next section. .. _`../rumur/resources/header.c`: ../rumur/resources/header.c Set Expansion ------------- When the set exceeds a pre-defined occupancy threshold, it is expanded by doubling its size. A simplified summary of how this is done is: 1. ``malloc()`` a new set twice the size of the existing one. 2. Take each state stored in the old set, rehash it, and insert it into the new set. 3. ``free()`` the old set. But how is this possible if the verifier is multithreaded and set insertion is lock-free? Firstly, when a thread decides to expand the seen state set it can either "win" the race to allocate the new set or "lose" the race and fall back to joining a set migration begun by a previous thread. During migration, threads grab successive "chunks" (4KB blocks of state pointers) to exclusively migrate by atomically incrementing a shared counter. As they migrate state pointers from the old set to the new, they replace the old state pointer with a "tombstone" value. We can now understand why ``set_insert()`` is checking for tombstones. If it sees a tombstone, it knows a set expansion and migration has been initiated by another thread. It can then hold off on its insertion, join the migration effort, then return to attempting its insertion on the new set. At the end of a set migration, all threads synchronise at a rendezvous point (to be described in forthcoming documentation). The last arriving thread to the rendezvous point updates the global seen set pointer to point to the new set. When threads depart the rendezvous point, they borrow a new local copy of this pointer. A Note on Complexity -------------------- The seen state set is one of the most complex and performance sensitive components of the verifier. Given I am not a professional technical writer, it is unlikely you have fully understood how it works from the above description. If you are interested in learning more, I encourage you to read the source in `../rumur/resources/header.c`_. It is dense and the control flow can be counter intuitive, but unfortunately I did not have more time to make it simpler. rumur-2020.02.17/doc/internals-warts.rst000066400000000000000000000107461362265074000177300ustar00rootroot00000000000000Internals — Warts ================= This document covers some design flaws in Rumur that it would be nice to find a solution to at some point. Non-minimal traces when running multithreaded --------------------------------------------- * `Github issue #131 “minimal trace mode”`_ Rumur defaults to generating a multithreaded verifier (``--threads 0``) that aborts as soon as it finds an error (``--max-errors 1``). This was seen to be pragmatic and aligning with what users typically want to do when bringing up a new model. That is, run as fast as possible and stop as soon as we know an exhaustive proof is impossible. When examining counterexample traces, you generally want to be presented with the shortest possible example. This happens naturally in a single-threaded verifier as it performs a breadth-first search. The multithreaded verifier is only approximately breadth-first because all the threads are essentially racing with each other in their exploration. As a result, the first problem found may not be the shallowest and may result in a non-minimal counterexample trace. Each thread encounters states in greater-than-or-equal-depth order, so a naive attempt at forcing a minimal trace is to temporarily stall a thread when it finds an error. We can unblock this thread and allow it to report its error when all other threads have either (a) finished exploration or (b) exceeded the depth of the error location found by the first thread. If a second thread finds an error at a shallower depth than the first, it can simply kill the first thread and then itself stall as described. Alas, this is insufficient. Taking the example from the Github issue, the state machine may look as follows: .. code-block:: A -> B -> C -> ... \ ^ \______/ Thread 0 might discover ``C`` first via the path ``A -> B -> C``. Later thread 1 might rediscover ``C`` via the path ``A -> C``. Thread 1 will notice its ``C`` is a duplicate and discard it. Now suppose one of the threads finds a deeper error whose counterexample traces back to ``C``. This counterexample will trace back through the ``A -> B`` prefix rather than the shorter ``A``. So far I have not come up with a design that would result in a minimal counterexample trace in a multithreaded verifier without degrading current performance. Solving this would, I believe, be of significant value to users. .. _`Github issue #131 “minimal trace mode”`: https://github.com/Smattr/rumur/issues/131 Incomplete AST in recursive functions ------------------------------------- The librumur Abstract Syntax Tree (AST) represents child nodes using smart pointers, ``rumur::Ptr``. This has constructor and assignment operator implementations that allow the programmer to avoid thinking too much about memory management. The general pattern is to freely copy the pointed-to object into a new ``rumur::Ptr``. This isn’t very efficient, but has the advantage of being simple. Moreover, there is little point tightly optimising AST manipulation when any AST that is large enough to see benefit from this most likely results from a model that is too large to verify. A side-effect of this “deep copy” pointer design is that the AST is inherently a Directed Acyclic Graph (DAG). It is not possible for an AST node to contain a reference to something above itself in the AST because assigning this reference takes a copy of the parent node. This means that during symbol resolution, a recursive function call has no way to store a fully resolved reference to its callee. To see why this is, follow through the symbol resolution process. To begin with, the containing function has a statement that includes an unresolved function call. The symbol resolution traversal finds this unresolved function call and resolves it to the containing function by copying it. However, this copy still contains the unresolved version of the function call as a child. We can descend into the callee function (``rumur::FunctionCall::function``) but we’ll merely repeat the process and have a deeper unresolved function call. The current design merely avoids descending into the callees of function calls. This is acceptable, but it leaves an AST containing unresolved function calls that may come as a surprise to later consumers. Obtaining a fully resolved AST while supporting recursion seems inherently at odds with the current design. It seems like it would be a net loss to permit cycles in the AST (we would have to manually manage pointers and memory) but perhaps there is another compromise we could find. rumur-2020.02.17/doc/intro-to-murphi.rst000066400000000000000000000543361362265074000176530ustar00rootroot00000000000000Introduction to Murphi ====================== The input syntax accepted by Rumur is a variant of a language called “Murphi” that was originally designed for use in a different model checker. The following provides an introduction to the language by way of a simple example that we gradually build upon. Overview -------- Murphi models consist of three sections: 1. Definitions of constants, types and state variables; 2. Functions and procedures; and 3. State transition rules and invariants. There are no specific characters that mark the transition between these sections but the order in which model elements can appear is constrained by this. For example, state variables cannot be defined after a function. The Dining Philosophers ----------------------- Let’s think about a classic concurrency problem, the dining philosophers. Five philosophers go out for a spaghetti dinner. They sit around a circular table with five plates of spaghetti and five forks, placed between the plates. Each philosopher needs two forks to eat the plate of spaghetti in front of them. This is a system that can be thought of as a state machine. At each point in time, a philosopher can either be thinking, hungry and trying to eat, or eating. The moves they can each make are to pick up a fork or put down a fork. We want to ensure that all philosophers eventually get to eat. Sounds pretty simple, right? Building a Model ---------------- We can use Rumur to prove all the philosophers will eventually get to eat. To do this, we construct a Murphi model describing the dining philosophers. Our philosophy dinner has five participants, but maybe in future we will want to think about more than five philosophers. So let’s plan ahead and use a Murphi constant that we can easily update later. .. code-block:: murphi const N_PHILOSOPHERS: 5 We always have the same number of forks as philosophers, so let’s define that too. .. code-block:: murphi const N_FORKS N_PHILOSOPHERS We can identify each philosopher and each fork by an identifier from 0 to 4. .. code-block:: murphi type philosopher_id: 0 .. N_PHILOSOPHERS - 1 type fork_id: 0 .. N_FORKS - 1 The philosophers can be thinking, hungry or eating. We can represent this in Murphi with an enumerated type. .. code-block:: murphi type action: enum { THINKING, HUNGRY, EATING } We can now represent a philosopher as a record (like a C struct) capturing their mood and which forks they have. .. code-block:: murphi type philosopher: record mood: action has_left_fork: boolean has_right_fork: boolean end We’re now ready to define the state of the system. This will be the philosophers themselves and an array indicating which forks are held. .. code-block:: murphi var philosophers: array[philosopher_id] of philosopher var forks: array[fork_id] of boolean This completes the first section of our dining philosophers model. We have seen constants, all the basic types, and how to define state variables. The keywords ``const``, ``type``, and ``var`` are not actually attached to individual declarations but rather to sections. So we could write a condensed version of what we have so far as follows. .. code-block:: murphi const N_PHILOSOPHERS: 5 N_FORKS: N_PHILOSOPHERS type philosopher_id: 0 .. N_PHILOSOPHERS - 1 fork_id: 0 .. N_FORKS - 1 action: enum { THINKING, HUNGRY, EATING } philosopher: record mood: action has_left_fork: boolean has_right_fork: boolean end var philosophers: array[philosopher_id] of philosopher forks: array[fork_id] of boolean OK, time for some helper functions. We already know we will need to refer to the forks on either side of a philosopher. So let’s define functions for these to avoid having to write awkward modular arithmetic repeatedly. .. code-block:: murphi function left_fork(id: philosopher_id): fork_id; begin return id; end function right_fork(id: philosopher_id): fork_id; begin return (id + 1) % N_PHILOSOPHERS; end Note that the way parameters and return types occur might be back to front to what you expect from, for example, C. Function parameters are given as ``name: type`` and the return type appears after the function’s closing bracket. We can now start defining the state transition rules for our system. These are (optionally guarded) blocks that describe updates to the state. All the statements within a rule execute atomically. That is, the entire block as a whole represents a transition from one system state to another. The first one of these rules is the start state that describes how to initialise our system. .. code-block:: murphi startstate begin -- all philosophers start dinner hungry and empty handed for i: philosopher_id do philosophers[i].mood := HUNGRY; philosophers[i].has_left_fork := false; philosophers[i].has_right_fork := false; end; -- so all forks are unheld for i: fork_id do forks[i] := false; end; end A Murphi model can have multiple start states. However we will only use one in this model. What do the philosophers do when they are hungry? They try to grab forks to eat. To write a transition rule for this we do not want to talk about any particular philosopher but rather *any* of the five philosophers. We can do this with a rule set. .. code-block:: murphi ruleset i: philosopher_id do rule "take left fork" philosophers[i].mood = HUNGRY -- wants to eat & !philosophers[i].has_left_fork -- doesn’t have the left fork & !forks[left_fork(i)] -- left fork is available ==> begin forks[left_fork(i)] := true; philosophers[i].has_left_fork := true; end rule "take right fork" philosophers[i].mood = HUNGRY & !philosophers[i].has_right_fork & !forks[right_fork(i)] ==> begin forks[right_fork(i)] := true; philosophers[i].has_right_fork := true; end end If a philosopher has both their forks and they are hungry, they can start eating. .. code-block:: murphi ruleset i: philosopher_id do rule "eat" philosophers[i].mood = HUNGRY -- wants to eat & philosophers[i].has_left_fork & philosophers[i].has_right_fork -- has both forks ==> begin philosophers[i].mood := EATING; end end Once they have been eating, a philosopher may get full and decide to take a break and think for a while. Note that at our dinner philosophers cannot start thinking when they are hungry because they are too distracted by their stomach rumbling. .. code-block:: murphi ruleset i: philosopher_id do rule "think" philosophers[i].mood = EATING ==> begin philosophers[i].mood := THINKING; end end A thinking philosopher may always be struck by hunger again. .. code-block:: murphi ruleset i: philosopher_id do rule "get hungry" philosophers[i].mood = THINKING ==> begin philosophers[i].mood := HUNGRY; end end Finally, a philosopher who is thinking but also holding forks places them back on the table as they ponder the mysteries of the universe. .. code-block:: murphi ruleset i: philosopher_id do rule "drop left fork" philosophers[i].mood = THINKING & philosophers[i].has_left_fork ==> begin -- sanity check that the fork we are releasing was held assert forks[left_fork(i)]; forks[left_fork(i)] := false; philosophers[i].has_left_fork := false; end rule "drop right fork" philosophers[i].mood = THINKING & philosophers[i].has_right_fork ==> begin assert forks[right_fork(i)]; forks[right_fork(i)] := false; philosophers[i].has_right_fork := false; end end These are all the transition rules we need to describe the dining philosophers. Let’s add an invariant; something that should always be true. Each fork can only be held by a single philosopher, so let’s claim that a philosopher holding their right fork is always next to a philosopher missing their left fork. .. code-block:: murphi invariant "no fork sharing" -- for any philosopher... forall i: philosopher_id do -- ...either they do not have their right fork... !philosophers[i].has_right_fork -- ...or their neighbour does not have their left fork | !philosophers[(i + 1) % N_PHILOSOPHERS].has_left_fork end And that’s it! We have built a model of the dining philosophers. Let’s put is altogether and merge the rule sets into a single block. .. code-block:: murphi const N_PHILOSOPHERS: 5 N_FORKS: N_PHILOSOPHERS type philosopher_id: 0 .. N_PHILOSOPHERS - 1 fork_id: 0 .. N_FORKS - 1 action: enum { THINKING, HUNGRY, EATING } philosopher: record mood: action has_left_fork: boolean has_right_fork: boolean end var philosophers: array[philosopher_id] of philosopher forks: array[fork_id] of boolean function left_fork(id: philosopher_id): fork_id; begin return id; end function right_fork(id: philosopher_id): fork_id; begin return (id + 1) % N_PHILOSOPHERS; end startstate begin -- all philosophers start dinner hungry and empty handed for i: philosopher_id do philosophers[i].mood := HUNGRY; philosophers[i].has_left_fork := false; philosophers[i].has_right_fork := false; end; -- so all forks are unheld for i: fork_id do forks[i] := false; end; end ruleset i: philosopher_id do rule "take left fork" philosophers[i].mood = HUNGRY -- wants to eat & !philosophers[i].has_left_fork -- doesn’t have the left fork & !forks[left_fork(i)] -- left fork is available ==> begin forks[left_fork(i)] := true; philosophers[i].has_left_fork := true; end rule "take right fork" philosophers[i].mood = HUNGRY & !philosophers[i].has_right_fork & !forks[right_fork(i)] ==> begin forks[right_fork(i)] := true; philosophers[i].has_right_fork := true; end rule "eat" philosophers[i].mood = HUNGRY -- wants to eat & philosophers[i].has_left_fork & philosophers[i].has_right_fork -- has both forks ==> begin philosophers[i].mood := EATING; end rule "think" philosophers[i].mood = EATING ==> begin philosophers[i].mood := THINKING; end rule "get hungry" philosophers[i].mood = THINKING ==> begin philosophers[i].mood := HUNGRY; end rule "drop left fork" philosophers[i].mood = THINKING & philosophers[i].has_left_fork ==> begin assert forks[left_fork(i)]; forks[left_fork(i)] := false; philosophers[i].has_left_fork := false; end rule "drop right fork" philosophers[i].mood = THINKING & philosophers[i].has_right_fork ==> begin assert forks[right_fork(i)]; forks[right_fork(i)] := false; philosophers[i].has_right_fork := false; end end invariant "no fork sharing" -- for any philosopher... forall i: philosopher_id do -- ...either they do not have their right fork... !philosophers[i].has_right_fork -- ...or their neighbour does not have their left fork | !philosophers[(i + 1) % N_PHILOSOPHERS].has_left_fork end Now to see what Rumur can tell us about this system... Verifying the Model ------------------- We can take the model we have just written and ask Rumur to produce a program to verify it. That is, Rumur will generate a separate C program that captures the meaning of our model and will check its properties for us. Save the model as philosophers.m and then run the following. .. code-block:: sh rumur --output philosophers.c philosophers.m We now have a file, philosophers.c, that is our verifier. We can compile this with a C compiler. .. code-block:: sh # if you are using an x86-64 machine, also add -mcx16 cc -std=c11 -O3 -o philosophers philosophers.c -lpthread Now we can run the verifier to try and prove our model correct. .. code-block:: sh ./philosophers Memory usage: * The size of each state is 40 bits (rounded up to 5 bytes). * The size of the hash table is 32768 slots. Progress Report: The following is the error trace for the error: deadlock Startstate 1 fired. philosophers[0].mood:HUNGRY philosophers[0].has_left_fork:false philosophers[0].has_right_fork:false philosophers[1].mood:HUNGRY philosophers[1].has_left_fork:false philosophers[1].has_right_fork:false philosophers[2].mood:HUNGRY philosophers[2].has_left_fork:false philosophers[2].has_right_fork:false philosophers[3].mood:HUNGRY philosophers[3].has_left_fork:false philosophers[3].has_right_fork:false philosophers[4].mood:HUNGRY philosophers[4].has_left_fork:false philosophers[4].has_right_fork:false forks[0]:false forks[1]:false forks[2]:false forks[3]:false forks[4]:false ---------- Rule "take left fork", i: 0 fired. philosophers[0].has_left_fork:true forks[0]:true ---------- Rule "take left fork", i: 1 fired. philosophers[1].has_left_fork:true forks[1]:true ---------- Rule "take left fork", i: 2 fired. philosophers[2].has_left_fork:true forks[2]:true ---------- Rule "take left fork", i: 3 fired. philosophers[3].has_left_fork:true forks[3]:true ---------- Rule "take left fork", i: 4 fired. philosophers[4].has_left_fork:true forks[4]:true ---------- End of the error trace. ========================================================================== Status: 1 error(s) found. State Space Explored: 597 states, 1734 rules fired in 0s. Hm, it pretty clearly failed but why? And what does all of this output mean? We can see the failure cause at towards the beginning of the output, “deadlock.” The verifier found a sequence of transitions that would result in a state where none of the philosophers could make a move. The blocks of output following the error itself give a counterexample trace. This shows the exact path of rule transitions needed to reproduce the deadlocked state. By following this, you can see we have found a well known problem with this classic example, where each philosopher takes the fork to their left. After this, all philosophers are wanting the fork to their right but the fork to the right of each philosopher is already held by their neighbour. There are a couple of noteworthy points here. Rumur found a problem in our model quickly and without us having to guide it. It also found a problem that was not a violation of our invariant, but a problem it knew to check for anyway. By default, Rumur considers a deadlock of your model to be an error condition. Fixing the Model ---------------- So the design of our system is incorrect, and model checking helped us find the problem with it. How do we go about correcting it? Let’s take one of the known solutions to this problem and order the resources (forks) that are being acquired. Instead of letting a philosopher take any fork, we say they can only take the free fork with the lowest identifier of the two they need. .. code-block:: diff ruleset i: philosopher_id do rule "take left fork" philosophers[i].mood = HUNGRY -- wants to eat & !philosophers[i].has_left_fork -- doesn’t have the left fork + -- either already has the right fork or the left is lower numbered + & (philosophers[i].has_right_fork | left_fork(i) < right_fork(i)) & !forks[left_fork(i)] -- left fork is available ==> begin forks[left_fork(i)] := true; philosophers[i].has_left_fork := true; end rule "take right fork" philosophers[i].mood = HUNGRY & !philosophers[i].has_right_fork + & (philosophers[i].has_left_fork | left_fork(i) > right_fork(i)) & !forks[right_fork(i)] ==> begin forks[right_fork(i)] := true; philosophers[i].has_right_fork := true; end end We also need to apply this ordering on fork release, and only allow philosophers to release forks in descending order. .. code-block:: diff ruleset i: philosopher_id do rule "drop left fork" philosophers[i].mood = THINKING & philosophers[i].has_left_fork + -- either right is not held or the left is higher numbered + & (!philosophers[i].has_right_fork | left_fork(i) > right_fork(i)) ==> begin assert forks[left_fork(i)]; forks[left_fork(i)] := false; philosophers[i].has_left_fork := false; end rule "drop right fork" philosophers[i].mood = THINKING & philosophers[i].has_right_fork + & (!philosophers[i].has_left_fork | left_fork(i) < right_fork(i)) ==> begin assert forks[right_fork(i)]; forks[right_fork(i)] := false; philosophers[i].has_right_fork := false; end end Is this enough? Let’s ask Rumur. .. code-block:: sh rumur --output philosophers.c philosophers.m cc -std=c11 -O3 -o philosophers philosophers.c -lpthread ./philosophers Memory usage: * The size of each state is 40 bits (rounded up to 5 bytes). * The size of the hash table is 32768 slots. Progress Report: ========================================================================== Status: No error found. State Space Explored: 3216 states, 16160 rules fired in 0s. Hooray! Our model is deadlock free and our invariant is proven. From here, maybe we would like to increase the number of philosophers or make the dining arrangement more complicated. We could also introduce further invariants to check the model more thoroughly. But hopefully you have already seen enough to understand the value of model checking and have some ideas about how to write models of your own systems. Bonus - Liveness ---------------- At this point, we have proven that the philosophers do not get stuck and do not share forks, but we have not shown that each philosopher does get the chance to eat. In fact, we would like to go even further than that and prove that they are always able to eat.[1]_ To do this, we write a liveness property. .. [1] This might be phrased as that they are able to eat infinitely often, but this sounds a little peculiar. .. code-block:: murphi ruleset i: philosopher_id do liveness "can eventually eat" philosophers[i].mood = EATING end We need to rebuild and compile our model, then run it to see the result. .. code-block:: sh rumur --output philosophers.c philosophers.m cc -std=c11 -O3 -o philosophers philosophers.c -lpthread ./philosophers Memory usage: * The size of each state is 40 bits (rounded up to 5 bytes). * The size of the hash table is 32768 slots. Progress Report: ========================================================================== Status: No error found. State Space Explored: 3216 states, 16160 rules fired in 0s. And with that, we have proven that our philosophers will not starve but can all eat as much as they please. For reference, the full model we constructed is given below. .. code-block:: murphi const N_PHILOSOPHERS: 5 N_FORKS: N_PHILOSOPHERS type philosopher_id: 0 .. N_PHILOSOPHERS - 1 fork_id: 0 .. N_FORKS - 1 action: enum { THINKING, HUNGRY, EATING } philosopher: record mood: action has_left_fork: boolean has_right_fork: boolean end var philosophers: array[philosopher_id] of philosopher forks: array[fork_id] of boolean function left_fork(id: philosopher_id): fork_id; begin return id; end function right_fork(id: philosopher_id): fork_id; begin return (id + 1) % N_PHILOSOPHERS; end startstate begin -- all philosophers start dinner hungry and empty handed for i: philosopher_id do philosophers[i].mood := HUNGRY; philosophers[i].has_left_fork := false; philosophers[i].has_right_fork := false; end; -- so all forks are unheld for i: fork_id do forks[i] := false; end; end ruleset i: philosopher_id do rule "take left fork" philosophers[i].mood = HUNGRY -- wants to eat & !philosophers[i].has_left_fork -- doesn’t have the left fork -- either already has the right fork or the left is lower numbered & (philosophers[i].has_right_fork | left_fork(i) < right_fork(i)) & !forks[left_fork(i)] -- left fork is available ==> begin forks[left_fork(i)] := true; philosophers[i].has_left_fork := true; end rule "take right fork" philosophers[i].mood = HUNGRY & !philosophers[i].has_right_fork & (philosophers[i].has_left_fork | left_fork(i) > right_fork(i)) & !forks[right_fork(i)] ==> begin forks[right_fork(i)] := true; philosophers[i].has_right_fork := true; end rule "eat" philosophers[i].mood = HUNGRY -- wants to eat & philosophers[i].has_left_fork & philosophers[i].has_right_fork -- has both forks ==> begin philosophers[i].mood := EATING; end rule "think" philosophers[i].mood = EATING ==> begin philosophers[i].mood := THINKING; end rule "get hungry" philosophers[i].mood = THINKING ==> begin philosophers[i].mood := HUNGRY; end rule "drop left fork" philosophers[i].mood = THINKING & philosophers[i].has_left_fork -- either right is not held or the left is higher numbered & (!philosophers[i].has_right_fork | left_fork(i) > right_fork(i)) ==> begin assert forks[left_fork(i)]; forks[left_fork(i)] := false; philosophers[i].has_left_fork := false; end rule "drop right fork" philosophers[i].mood = THINKING & philosophers[i].has_right_fork & (!philosophers[i].has_left_fork | left_fork(i) < right_fork(i)) ==> begin assert forks[right_fork(i)]; forks[right_fork(i)] := false; philosophers[i].has_right_fork := false; end end invariant "no fork sharing" -- for any philosopher... forall i: philosopher_id do -- ...either they do not have their right fork... !philosophers[i].has_right_fork -- ...or their neighbour does not have their left fork | !philosophers[(i + 1) % N_PHILOSOPHERS].has_left_fork end ruleset i: philosopher_id do liveness "can eventually eat" philosophers[i].mood = EATING end rumur-2020.02.17/doc/introduction.rst000066400000000000000000000051431362265074000173070ustar00rootroot00000000000000An Introduction to Rumur ======================== Rumur is a tool for formal verification of complex systems. It falls into a family known as “model checkers.” This document gives a brief introduction to formal verification and model checking, aimed at helping Rumur users understand at a high level how the tool works. Formal Verification ------------------- When building a complex system, you would like to know that it is correct. “Correct” is subjective and something specific to a particular system, but a person building a system generally knows what they expect that system to do (and what they expect it *not* to do). The traditional way of gaining confidence in a piece of software is to write tests. But as Dijkstra famously said, “testing can be used to show the presence of bugs, but never to show their absence.” Formal verification approaches this problem from the other direction. Instead of writing tests to give your system specific inputs and relying on the cleverness of the test author to exercise all the system’s edge cases, formal verification covers *all possible inputs*. When first encountering this family of techniques they may seem like magic, and some of the more advanced formal verification approaches are indeed complex to understand. However, Rumur is a particular type of formal verification tool called a “model checker” whose operation is actually quite simple, as we shall see. Model Checking -------------- Suppose you built a system that took the result of the roll of a six-sided die as input. What tests would you write? One test for each of the inputs (1, 2, 3, 4, 5, 6) would cover all possible scenarios. This is what model checking does on a larger scale. It simply tries every possible path through your system. Of course, for most systems this is not possible. For example, suppose the die you were rolling had 2⁶⁴ sides. You cannot try this many values in a reasonable amount of time. Here the key is to *abstract* your system. Does this larger system behave sufficiently differently to the smaller one that takes a six-sided die? If not, maybe it is reasonable to verify the correctness of the smaller system as a stand-in for the larger one. While the type of abstraction just described needs to be done by hand, there are certain types of abstraction that can be done automatically. Some model checking tools can identify *symmetries* in your system; situations where two paths through a system are equivalent and only one need be explored. With real world systems, this can be a powerful optimisation that transforms infeasible verification problems into something achievable. rumur-2020.02.17/doc/performance.rst000066400000000000000000000011541362265074000170650ustar00rootroot00000000000000Performance Summary =================== Rumur comfortably outperforms all other Murphi-based model checkers (as far as I am aware). There is no single magic bullet by which it achieves this, but rather a combination of the following techniques: 1. Multicore parallelism 2. Specialised data structures: a. Lock-free, insert-only seen state set b. Lock-free per-thread pending state queues 3. Bump-pointer, non-freeing state allocation To learn more about any of these, read the source of ../rumur/resources/header.c. The above list should give you a good intuition of what to expect to find in the source code. rumur-2020.02.17/doc/properties.rst000066400000000000000000000167001362265074000167630ustar00rootroot00000000000000Properties ========== When creating a model of a system, an important part to include is its definition of correctness. Rumur gives you four different types of global properties you can write: invariants, covers, assumptions, and liveness. Each of these except liveness can also be used locally (within a function, procedure, or rule). However, the local equivalent of an invariant is known as an assertion. Invariants ---------- An invariant is something you claim is always true following the execution of a rule. That is, a global correctness criterion for your system. You state an invariant with the ``invariant`` keyword. For example, you might model a counter that always has an even value: .. code-block:: murphi var counter: 0 .. 10; startstate begin counter := 0; end; rule "increment" counter < 10 ==> begin counter := counter + 2; end; rule "decrement" counter > 0 ==> begin counter := counter - 2; end; invariant "counter is always even" counter % 2 = 0; When you generate a verifier for this model and then run it, the verifier will check each state satisfies the condition ``counter % 2 = 0``. You can observe this by changing the property to something that will fail, like ``counter % 4 = 0``, and re-checking the model. Invariants can appear within rulesets, so you can write a parameterised invariant: .. code-block:: murphi ruleset x: 0 .. 10 do invariant "x is small" x < 100; end; This example is equivalent to: .. code-block:: murphi invariant "x is small" forall x: 0 .. 10 do x < 100 end; but sometimes it is more natural to use the ruleset style of writing. The text given to describe an invariant is optional, but it is good practice to include a description of the property to make invariant failure messages clearer. The keyword ``invariant`` and the keyword ``assert`` (see below) are treated as synonyms by Rumur, but it is good practice to use ``invariant`` for top level properties to better indicate your intentions to readers. Assertions ---------- Assertions are similar to invariants, but are only expected to hold locally at the point at which they are executed. You may be familiar with them from conventional programming languages. Re-using the counter example from above, you could add an assertion to the "increment" rule: .. code-block:: murphi rule "increment" counter < 10 ==> begin counter := counter + 2; assert "counter is non-zero" counter != 0; end; When the "increment" rule is run, this condition will be checked after incrementing ``counter``. Like invariants, the message is optional but encouraged. As mentioned above, ``invariant`` and ``assert`` are synonyms but it is recommended to use ``assert`` for local properties to aid readability. Covers ------ Similar to invariants and assertions, covers express a property you expect to hold but not necessarily every time it is checked. A cover is typically used to describe a reachability condition of your system. The generated checker counts how many times a cover property was found to be true and considers it an error if there is a cover that was never found to be true. Using our previous counter model, you could write a global cover property to ensure the counter is ``8`` at some point during execution: .. code-block:: murphi cover "counter seen as 8" counter = 8; Covers can be either global (like the example above) or local. You can use a local cover to check path coverage within a rule. For example, .. code-block:: murphi rule "increment" counter < 10 ==> begin if counter = 2 then cover "if branch covered" true; counter := counter + 4; else cover "else branch covered" true; counter := counter + 2; end; end; If you want to claim something is always true but also count the number of times you check the property, you can combine an assertion and a cover: .. code-block:: murphi rule "increment" counter < 10 ==> begin counter := counter + 2; assert "counter is even" counter % 2 = 0; cover "counter is even" counter % 2 = 0; end; Assumptions ----------- The properties discussed thus far allow you to write conditions you want to check hold in your system. Assumptions are a way to describe conditions you do not want to check but wish to assume of the environment your system operates in. For example, the "increment" rule could be written without a guard, instead using an assumption to avoid thinking about overflow: .. code-block:: murphi rule "increment" begin assume "counter within bounds" counter < 10; counter := counter + 2; end; This causes any transition that encounters a value of ``counter`` not less than ``10`` to be considered invalid. Like the other properties, assumptions can be either global or local, so we could write a global assumption that prevents the counter ever reaching ``10``: .. code-block:: murphi assume "clamp counter" counter < 8; For the reader who is curious about how this is implemented, the generated verifier discards any state that violates an assumption. That is, an assumption failure (either local within a rule or global after a rule transition) causes the invariant and cover checks to be skipped and the resulting (invalid) state to be ignored. Liveness -------- Liveness properties express something that should be reachable from every state. That is, a property that may not be true in a given state but will be true in at least one of the successors of every state. Using the counter example, we might write, .. code-block:: murphi liveness "counter can always become 4" counter = 4; to capture that we should always be able to reach the state where ``counter`` is ``4``. Unlike the other property types, liveness can only be used globally and not locally as a statement. Sometimes you might want to write a conditional liveness property. For example, that a variable ``x`` can always become ``1`` but only once you are out of some initial setup phase and into the steady state of your system. You can do this by also referencing a variable that captures the phase of your system in the liveness property. For example, .. code-block:: murphi type phase_t: enum { SETUP, RUN }; var phase: phase_t; halt: boolean; x: 0 .. 10; startstate begin phase := SETUP; halt := false; end; ruleset has_err: boolean do rule "init" !halt & phase = SETUP ==> begin if has_err then halt := true; else x := 0; phase := RUN; end; end; end; rule phase = RUN & x < 10 ==> begin x := x + 1; end; rule phase = RUN & x > 0 ==> begin x := x - 1; end; liveness "x can be 1" phase = SETUP | x = 1; For large models, note that Rumur's algorithm for checking liveness properties is not as efficient as other types of properties. You may find that checking a liveness properties on a large state space requires a long time. Relationship to Linear Temporal Logic ------------------------------------- Readers familiar with Linear Temporal Logic (LTL) might notice a similarity in the properties supported by Rumur and the expressibility of LTL. Rumur's available properties are more constrained than LTL. To be pragmatic, they are limited to some that can be checked efficiently within an explicit state model checking algorithm. Invariants roughly correspond to LTL's "always" operator, ``G`` or ``□``. Liveness roughly corresponds to "always eventually", ``G F`` or ``□ ◊``. The other types of properties do not have direct LTL equivalents. rumur-2020.02.17/doc/release-checklist.rst000066400000000000000000000103011362265074000201450ustar00rootroot00000000000000Release Checklist ================= The process of cutting a new Rumur release should follow these steps: 1. Update ``LAST_RELEASE`` in ../librumur/src/make-version.py. For version numbers, we use “vYYYY.MM.DD” with the date of the release. 2. Update ../CHANGELOG.rst with information about the changes in the new release. Changes should be separated into “user-facing” and “internal,” providing commit hash(es) as a reference where possible. The ordering in which changes are listed should firstly prioritise bug fixes (which should be explicitly marked as such) and then by the impact on users/developers a change will have. Code changes that are only of relevance to people hacking on Rumur can be omitted, and this audience can read about them in the Git log. 3. Commit this to master. 4. Push this to `upstream on Github`_. 5. Wait for the `Travis CI regression tests`_ to pass. Travis is not very reliable and many errors are caused by infrastructure failures rather than your actual changes. So if you get a failure, check the logs to make sure it is not a false positive. 6. Wait for the `Cirrus CI FreeBSD tests`_ to pass. It is important for the new release to work on FreeBSD because Rumur is in `FreeBSD’s package repository`_ and new releases are pulled in automatically. If one of these tests fail, you may need to look at the raw log because the summary output hides some stderr lines. 7. Tag the commit with the version number in “vYYYY.MM.DD” format. 8. Push the new version tag upstream. 9. Package Rumur for Debian (see below). Github’s automated release process should notice the version tag and show the new release as a downloadable zip/tarball on the “releases” tab of https://github.com/Smattr/rumur. Packaging for Debian -------------------- Rumur is currently `packaged in Debian unstable`_. To update the Debian Rumur package follow these steps. 1. Switch to the branch packaging/debian. 2. Merge from master. 3. Update the Debian changelog (../debian/changelog). Debian provide guidance on the `changelog format`_. 4. Commit these changes. 5. Push this upstream to packaging/debian. Then on a `Debian Unstable installation`_: .. code-block:: sh # 6. Upgrade all packages. sudo apt update sudo apt upgrade # 7. Clone the packaging/debian branch. git clone https://github.com/Smattr/rumur -b packaging/debian cd rumur # 8. Run packaging script. ./misc/package-for-debian.sh # If any lintian errors or warnings were output, you will need to address # these and then return to step 4. # 9. Update pbuilder environment. You will need to already have a pbuilder # environment created, as described in misc/package-for-debian.sh. sudo pbuilder update # 10. Test the package under pbuilder pdebuild # 11. Sign the source package that pbuilder created. You will need GPG key(s) # configured. debsign ../rumur_*_source.changes # 12. Upload to the mentors inbox. You will need dput configured, as described # in misc/package-for-debian.sh. dput mentors ../rumur_*_source.changes In a few minutes you should get a confirmation email that the upload succeeded. Then: 13. Follow the instructions included in the confirmation email to send a `Request For Sponsorship`_ to the Debian Mentors mailing list. Hope that you get a reply from an interested party. Note that there could still be problems with the package that a sponsor may request you fix. For example, there is currently no easy way to smoke test the autopkgtests and these could fail, preventing migration of the package to the main repositories. .. _`changelog format`: https://www.debian.org/doc/manuals/maint-guide/dreq.en.html#changelog .. _`Cirrus CI FreeBSD tests`: https://cirrus-ci.com/github/Smattr/rumur .. _`Debian Unstable installation`: https://wiki.debian.org/DebianUnstable#Installation .. _`FreeBSD’s package repository`: https://svnweb.freebsd.org/ports/head/math/rumur/ .. _`packaged in Debian unstable`: https://packages.debian.org/sid/rumur .. _`Request For Sponsorship`: https://mentors.debian.net/sponsor/rfs-howto .. _`upstream on Github`: https://github.com/Smattr/rumur .. _`Travis CI regression tests`: https://travis-ci.org/Smattr/rumur/builds/ rumur-2020.02.17/doc/vs-cmurphi.rst000066400000000000000000000120711362265074000166610ustar00rootroot00000000000000Rumur vs CMurphi ================ Because Rumur is attempting to appeal to CMurphi users, it is helpful to compare the feature set of the two. In this document we give a brief run down of the relevant differences. Performance Characteristics --------------------------- CMurphi produces a single-threaded verifier. If you have a multicore system, the verifier will only take advantage of a single core. Rumur produces a multi-threaded verifier by default, which will use as many cores as you have available. You can tune how many threads are used with the ``--threads`` command line option when generating the verifier. CMurphi seems intended to run on Linux and older Unix-style platforms. Rumur should run on any POSIX operating system. However, the verifier’s code does make use of C extensions that may only be supported by GCC and Clang. Syntax Differences ------------------ CMurphi considers the operator ``==`` an error, while Rumur treats it as an alias of ``=`` that is usable in comparisons. This is mainly to ease the life of C/C++ programmers. Rumur supports various single character operator alternatives, e.g. ``≤``. These are nicer for presentation of a model in a mathematical context. These more concise operators are generally recommended for use in models that are expected to be read more often than they are edited. The keywords ``assert`` and ``invariant`` are distinct in CMurphi, whereas they are considered synonyms by Rumur. If one of these appears within a rule or function it is considered an assertion and if it appears at the top level it is considered an invariant. Rumur also accepts the optional string message of an assertion or invariant on either side of the asserted expression. Type System ----------- CMurphi supports real arithmetic using the ``real`` data type. Rumur does not support this type and there are no plans to implement this or any floating point support. Similarly, Rumur does not support the ``union`` type and there are no plans to add it. Assumptions ----------- In addition to assertions and invariants that are supported by CMurphi, Rumur supports assumptions with the keyword ``assume``. Any state that fails an assumption is considered irrelevant and discarded. You can read more about this in properties.rst_. .. _properties.rst: properties.rst Liveness -------- Another addition to the properties supported by CMurphi, is the Rumur-specific ``liveness``. From every state that is reached during checking, there must be a state reachable from it that satisfies the liveness property. You can read more about this in properties.rst_. Comparisons ----------- CMurphi has a limitation that ``=`` and ``!=`` can only be used to compare simple values (enums, ranges or scalarsets). Rumur lets you compare any compatible values; that is, it also supports records and arrays. The semantics are equivalent to writing out a long form comparison of every one of the complex values’ members. Command-line Options -------------------- CMurphi has a set of command-line options, and its generated verifier has another set of command-line options. Rumur has some similar command-line options, but its generated verifier has none at all. The intent with this design is to expose as much information to the C compiler as possible. In some cases this can make a significant difference by allowing your C compiler to more aggressively optimise during compilation. Deadlock -------- The verifier can detect “deadlocks” in your state graph, where there are no transitions that make progress. CMurphi considers a deadlock to have occurred in any state that only has enabled transitions that lead back to itself. Rumur has two deadlock modes that can be selected with the ``--deadlock-detection`` command line option: “stuttering” and “stuck”. Stuttering, the default, matches CMurphi’s definition. Stuck uses a weaker definition that considers a deadlock to have occurred only when a state has *no* enabled transitions. Loops ----- CMurphi requires the step in a quantifier expression to be a generation-time constant. I.e. in the code: .. code-block:: murphi for i := l to u by s do ... end; ``s`` must have a known constant value when the verifier is being generated. Rumur has no such restrictions. However steps that are ``0`` at generation-time are rejected and steps that turn out to be ``0`` or incrementing in the wrong direction at runtime will be raised as a runtime error by the verifier. CMurphi considers an infinite loop to be a runtime error that the verifier should be capable of detecting and notifying the user about. It has an iteration bound (by default 1000), after which it will consider a loop to be non-terminating and will raise an error. Rumur trusts the user not to write an infinite ``while`` loop. If you write an infinite loop, your verifier will run forever. You have been warned. Colour Output ------------- Rumur’s generated verifier attempts to imitate CMurphi’s verifier’s output to smooth the transition for users, but by default Rumur colourises its output using ANSI terminal sequences. This behaviour is controllable via command-line options. rumur-2020.02.17/librumur/000077500000000000000000000000001362265074000151255ustar00rootroot00000000000000rumur-2020.02.17/librumur/CMakeLists.txt000066400000000000000000000066131362265074000176730ustar00rootroot00000000000000project(librumur LANGUAGES CXX) find_package(BISON REQUIRED) find_package(FLEX REQUIRED) find_path(GMPXX_INCLUDE NAMES gmpxx.h PATHS ENV CPLUS_INCLUDE_PATH) if(NOT GMPXX_INCLUDE) message(FATAL_ERROR "GMP headers not found") endif() find_library(GMP_LIBRARIES NAMES gmp PATHS ENV LIBRARY_PATH) if(NOT GMP_LIBRARIES) message(FATAL_ERROR "libgmp not found") endif() find_library(GMPXX_LIBRARIES NAMES gmpxx PATHS ENV LIBRARY_PATH) if(NOT GMPXX_LIBRARIES) message(FATAL_ERROR "libgmpxx not found") endif() bison_target(parser src/parser.yy ${CMAKE_CURRENT_BINARY_DIR}/parser.yy.cc COMPILE_FLAGS --warnings=all) flex_target(lexer src/lexer.l ${CMAKE_CURRENT_BINARY_DIR}/lexer.l.cc) add_flex_bison_dependency(lexer parser) # Suppress warnings in generated code. include(CheckCXXCompilerFlag) set_source_files_properties(lexer.l.cc PROPERTIES COMPILE_FLAGS "") CHECK_CXX_COMPILER_FLAG(-Wsign-compare HAS_WARNING_SIGN_COMPARE) if(HAS_WARNING_SIGN_COMPARE) set_source_files_properties(lexer.l.cc PROPERTIES COMPILE_FLAGS "${LEXER_COMPILE_FLAGS} -Wno-sign-compare") endif() get_source_file_property(LEXER_COMPILE_FLAGS lexer.l.cc COMPILE_FLAGS) CHECK_CXX_COMPILER_FLAG(-Wregister HAS_WARNING_REGISTER) if(HAS_WARNING_REGISTER) set_source_files_properties(lexer.l.cc PROPERTIES COMPILE_FLAGS "${LEXER_COMPILE_FLAGS} -Wno-register") endif() get_source_file_property(LEXER_COMPILE_FLAGS lexer.l.cc COMPILE_FLAGS) CHECK_CXX_COMPILER_FLAG(-Wdeprecated-register HAS_WARNING_DEPRECATED_REGISTER) if(HAS_WARNING_DEPRECATED_REGISTER) set_source_files_properties(lexer.l.cc PROPERTIES COMPILE_FLAGS "${LEXER_COMPILE_FLAGS} -Wno-deprecated-register") endif() add_custom_command( OUTPUT rumur-get-version.h COMMAND src/make-version.py ${CMAKE_CURRENT_BINARY_DIR}/rumur-get-version.h MAIN_DEPENDENCY src/make-version.py DEPENDS always_run WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) # Dummy output to make sure we always re-evaluate the version step above. add_custom_command( OUTPUT always_run COMMAND /usr/bin/env true) add_library(librumur ${CMAKE_CURRENT_BINARY_DIR}/rumur-get-version.h src/Boolean.cc src/Decl.cc src/except.cc src/Expr.cc src/Function.cc src/indexer.cc src/Model.cc src/Node.cc src/Number.cc src/parse.cc src/Property.cc src/resolve-symbols.cc src/Rule.cc src/Stmt.cc src/traverse.cc src/TypeExpr.cc src/validate.cc ${FLEX_lexer_OUTPUTS} ${BISON_parser_OUTPUTS}) target_include_directories(librumur PUBLIC $ $ ${GMPXX_INCLUDE} PRIVATE src ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(librumur PUBLIC ${GMPXX_LIBRARIES} ${GMP_LIBRARIES}) # Force the output to librumur.a instead of liblibrumur.a. set_target_properties(librumur PROPERTIES PREFIX "") # Clagged boiler plate to install a library. install(TARGETS librumur EXPORT LibrumurConfig ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/location.hh ${CMAKE_CURRENT_BINARY_DIR}/parser.yy.hh ${CMAKE_CURRENT_BINARY_DIR}/position.hh ${CMAKE_CURRENT_BINARY_DIR}/rumur-get-version.h ${CMAKE_CURRENT_BINARY_DIR}/stack.hh DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rumur) export(TARGETS librumur FILE LibrumurConfig.cmake) rumur-2020.02.17/librumur/include/000077500000000000000000000000001362265074000165505ustar00rootroot00000000000000rumur-2020.02.17/librumur/include/rumur/000077500000000000000000000000001362265074000177225ustar00rootroot00000000000000rumur-2020.02.17/librumur/include/rumur/Boolean.h000066400000000000000000000003611362265074000214520ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace rumur { extern const Ptr Boolean; extern const Ptr False; extern const Ptr True; } rumur-2020.02.17/librumur/include/rumur/Decl.h000066400000000000000000000074711362265074000207530ustar00rootroot00000000000000#pragma once #include #include #include #include #include "location.hh" #include #include #include #include #include #include namespace rumur { struct Decl : public Node { std::string name; Decl(const std::string &name_, const location &loc_); virtual ~Decl() = 0; Decl *clone() const override = 0; }; struct ExprDecl : public Decl { ExprDecl(const std::string &name_, const location &loc_); virtual ~ExprDecl() = default; // Return true if this declaration is usable as an lvalue virtual bool is_lvalue() const = 0; /* Return true if this declaration is to a resource cannot be modified. Note * that this is only relevant for declarations for which is_lvalue() returns * true. For non-lvalue declarations, this is always true. */ virtual bool is_readonly() const = 0; virtual Ptr get_type() const = 0; ExprDecl *clone() const override = 0; }; struct AliasDecl : public ExprDecl { Ptr value; AliasDecl(const std::string &name_, const Ptr &value_, const location &loc_); AliasDecl *clone() const final; virtual ~AliasDecl() = default; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; bool is_lvalue() const final; bool is_readonly() const final; Ptr get_type() const final; }; struct ConstDecl : public ExprDecl { Ptr value; /* The type of this constant. Typically this will be NULL (untyped), but in * the case of enum members it will have the enum declaration as its type. */ Ptr type; ConstDecl(const std::string &name_, const Ptr &value_, const location &loc_); ConstDecl(const std::string &name_, const Ptr &value_, const Ptr &type_, const location &loc_); ConstDecl *clone() const final; virtual ~ConstDecl() = default; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; bool is_lvalue() const final; bool is_readonly() const final; void validate() const final; Ptr get_type() const final; }; struct TypeDecl : public Decl { Ptr value; TypeDecl(const std::string &name, const Ptr &value_, const location &loc); TypeDecl *clone() const final; virtual ~TypeDecl() = default; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; }; struct VarDecl : public ExprDecl { Ptr type; /* Offset within the model state. This is only relevant if this is a state * variable. We initially set it to an invalid value and rely on Model::reindex * setting this correctly later. */ mpz_class offset = -1; /* Whether this variable is a read-only reference. E.g. a non-var parameter to * a function or procedure. */ bool readonly = false; // DEPRECATED, DO NOT USE // Commented out because it triggers warnings when the copy ctor is invoked: // [[gnu::deprecated("state_variable has been replaced by is_in_state()")]] bool state_variable = false; VarDecl(const std::string &name_, const Ptr &type_, const location &loc_); VarDecl *clone() const final; virtual ~VarDecl() = default; mpz_class count() const; mpz_class width() const; /* Whether this variable declaration is a global; part of the state. That is, * not a local declaration, function parameter, etc. */ bool is_in_state() const; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; bool is_lvalue() const final; bool is_readonly() const final; Ptr get_type() const final; }; } rumur-2020.02.17/librumur/include/rumur/Expr.h000066400000000000000000000364521362265074000210230ustar00rootroot00000000000000#pragma once #include #include #include #include #include "location.hh" #include #include #include #include #include namespace rumur { // Forward declarations to avoid a circular #include struct ExprDecl; struct Function; struct TypeExpr; struct VarDecl; struct Expr : public Node { Expr(const location &loc_); virtual ~Expr() = default; virtual Expr *clone() const = 0; // Whether an expression is a compile-time constant virtual bool constant() const = 0; /* The type of this expression. A nullptr indicates the type is equivalent * to a numeric literal; that is, an unbounded range. */ virtual Ptr type() const = 0; // If this expression is of boolean type. bool is_boolean() const; virtual mpz_class constant_fold() const = 0; // Is this value valid to use on the LHS of an assignment? virtual bool is_lvalue() const; /* Is this value a constant (cannot be modified)? It only makes sense to ask * this of expressions for which is_lvalue() returns true. For non-lvalues, * this is always true. */ virtual bool is_readonly() const; // Get a string representation of this expression virtual std::string to_string() const = 0; // is this expression the boolean literal “true”? virtual bool is_literal_true() const; // is this expression the boolean literal “false”? virtual bool is_literal_false() const; }; struct Ternary : public Expr { Ptr cond; Ptr lhs; Ptr rhs; Ternary(const Ptr &cond_, const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Ternary() = default; Ternary *clone() const final; bool constant() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; std::string to_string() const final; /* Note we do not override is_lvalue. Unlike in C, ternary expressions are not * considered lvalues. */ }; struct BinaryExpr : public Expr { Ptr lhs; Ptr rhs; BinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~BinaryExpr() = default; BinaryExpr *clone() const override = 0; bool constant() const final; }; struct BooleanBinaryExpr : public BinaryExpr { BooleanBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); BooleanBinaryExpr() = delete; void validate() const final; }; struct Implication : public BooleanBinaryExpr { Implication(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); Implication *clone() const final; virtual ~Implication() = default; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct Or : public BooleanBinaryExpr { Or(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Or() = default; Or *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct And : public BooleanBinaryExpr { And(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~And() = default; And *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct UnaryExpr : public Expr { Ptr rhs; UnaryExpr(const Ptr &rhs_, const location &loc_); UnaryExpr *clone() const override = 0; virtual ~UnaryExpr() = default; bool constant() const final; }; struct Not : public UnaryExpr { Not(const Ptr &rhs_, const location &loc_); virtual ~Not() = default; Not *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; std::string to_string() const final; }; struct ComparisonBinaryExpr : public BinaryExpr { ComparisonBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); void validate() const final; }; struct Lt : public ComparisonBinaryExpr { Lt(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Lt() = default; Lt *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct Leq : public ComparisonBinaryExpr { Leq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Leq() = default; Leq *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct Gt : public ComparisonBinaryExpr { Gt(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Gt() = default; Gt *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct Geq : public ComparisonBinaryExpr { Geq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Geq() = default; Geq *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct EquatableBinaryExpr : public BinaryExpr { EquatableBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); void validate() const final; }; struct Eq : public EquatableBinaryExpr { Eq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Eq() = default; Eq *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct Neq : public EquatableBinaryExpr { Neq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Neq() = default; Neq *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct ArithmeticBinaryExpr : public BinaryExpr { ArithmeticBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); void validate() const final; }; struct Add : public ArithmeticBinaryExpr { Add(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Add() = default; Add *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct Sub : public ArithmeticBinaryExpr { Sub(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Sub() = default; Sub *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct Negative : public UnaryExpr { Negative(const Ptr &rhs_, const location &loc_); virtual ~Negative() = default; Negative *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; std::string to_string() const final; }; struct Mul : public ArithmeticBinaryExpr { Mul(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Mul() = default; Mul *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct Div : public ArithmeticBinaryExpr { Div(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Div() = default; Div *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct Mod : public ArithmeticBinaryExpr { Mod(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); virtual ~Mod() = default; Mod *clone() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct ExprID : public Expr { std::string id; Ptr value; ExprID(const std::string &id_, const Ptr &value_, const location &loc_); virtual ~ExprID() = default; ExprID *clone() const final; bool constant() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; bool is_lvalue() const final; bool is_readonly() const final; std::string to_string() const final; bool is_literal_true() const final; bool is_literal_false() const final; }; struct Field : public Expr { Ptr record; std::string field; Field(const Ptr &record_, const std::string &field_, const location &loc_); virtual ~Field() = default; Field *clone() const final; bool constant() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; bool is_lvalue() const final; bool is_readonly() const final; std::string to_string() const final; }; struct Element : public Expr { Ptr array; Ptr index; Element(const Ptr &array_, const Ptr &index_, const location &loc_); virtual ~Element() = default; Element *clone() const final; bool constant() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; bool is_lvalue() const final; bool is_readonly() const final; std::string to_string() const final; }; struct FunctionCall : public Expr { std::string name; Ptr function; std::vector> arguments; // Whether this is a child of a ProcedureCall bool within_procedure_call = false; FunctionCall(const std::string &name_, const std::vector> &arguments_, const location &loc_); virtual ~FunctionCall() = default; FunctionCall *clone() const final; bool constant() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; std::string to_string() const final; }; struct Quantifier : public Node { std::string name; // If this is != nullptr, the from/to/step will be nullptr Ptr type; Ptr from; Ptr to; Ptr step; Ptr decl; Quantifier(const std::string &name_, const Ptr &type_, const location &loc); Quantifier(const std::string &name_, const Ptr &from_, const Ptr &to_, const location &loc_); Quantifier(const std::string &name_, const Ptr &from_, const Ptr &to_, const Ptr &step_, const location &loc_); virtual ~Quantifier() = default; Quantifier *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; std::string to_string() const; // whether the quantifier's range can be constant folded bool constant() const; /* number of entries in this quantifier's range (only valid when constant() * returns true) */ mpz_class count() const; // get the lower bound of this quantified expression as a C expression std::string lower_bound() const; }; struct Exists : public Expr { Quantifier quantifier; Ptr expr; Exists(const Quantifier &quantifier_, const Ptr &expr_, const location &loc_); virtual ~Exists() = default; Exists *clone() const final; bool constant() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; std::string to_string() const final; }; struct Forall : public Expr { Quantifier quantifier; Ptr expr; Forall(const Quantifier &quantifier_, const Ptr &expr_, const location &loc_); virtual ~Forall() = default; Forall *clone() const final; bool constant() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; std::string to_string() const final; }; struct IsUndefined : public Expr { Ptr expr; IsUndefined(const Ptr &expr_, const location &loc_); virtual ~IsUndefined() = default; IsUndefined *clone() const final; bool constant() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; std::string to_string() const final; }; } rumur-2020.02.17/librumur/include/rumur/Function.h000066400000000000000000000020331362265074000216560ustar00rootroot00000000000000#pragma once #include #include "location.hh" #include #include #include #include #include #include #include #include namespace rumur { struct Function : public Node { std::string name; std::vector> parameters; Ptr return_type; std::vector> decls; std::vector> body; Function(const std::string &name_, const std::vector> ¶meters_, const Ptr &return_type_, const std::vector> &decls_, const std::vector> &body_, const location &loc_); virtual ~Function() = default; Function *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; // is this function side effect free? bool is_pure() const; // does this function contain calls to itself? bool is_recursive() const; }; } rumur-2020.02.17/librumur/include/rumur/Model.h000066400000000000000000000023721362265074000211370ustar00rootroot00000000000000#pragma once #include #include #include #include #include "location.hh" #include #include #include #include #include #include #include namespace rumur { struct Model : public Node { std::vector> decls; std::vector> functions; std::vector> rules; Model(const std::vector> &decls_, const std::vector> &functions_, const std::vector> &rules_, const location &loc_); virtual ~Model() = default; Model *clone() const final; // Get the size of the state data in bits. mpz_class size_bits() const; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; /* Get the number of global liveness properties in the model. Unlike * assumption_count, this considers the "flat" model. That is, a * ruleset-contained liveness property may count for more than one. */ mpz_class liveness_count() const; /* Update the bit offset of each variable declaration in the model and reindex * all AST nodes. */ void reindex(); }; } rumur-2020.02.17/librumur/include/rumur/Node.h000066400000000000000000000013721362265074000207630ustar00rootroot00000000000000#pragma once #include #include #include #include "location.hh" namespace rumur { struct Node { location loc; size_t unique_id = SIZE_MAX; Node() = delete; Node(const location &loc_); virtual ~Node() = default; virtual Node *clone() const = 0; // __attribute__((deprecated("operator== will be removed in a future release"))) virtual bool operator==(const Node &other) const = 0; // __attribute__((deprecated("operator!= will be removed in a future release"))) bool operator!=(const Node &other) const { return !(*this == other); } /* Confirm that data structure invariants hold. This function throws * rumur::Errors if invariants are violated. */ virtual void validate() const { } }; } rumur-2020.02.17/librumur/include/rumur/Number.h000066400000000000000000000016241362265074000213260ustar00rootroot00000000000000#pragma once #include #include #include #include #include "location.hh" #include #include #include #include namespace rumur { struct Number : public Expr { mpz_class value; Number() = delete; Number(const std::string &value_, const location &loc_); Number(const mpz_class &value_, const location &loc_); Number(const Number&) = default; Number(Number&&) = default; Number &operator=(const Number&) = default; Number &operator=(Number&&) = default; virtual ~Number() = default; Number *clone() const final; bool constant() const final; Ptr type() const final; mpz_class constant_fold() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; } rumur-2020.02.17/librumur/include/rumur/Property.h000066400000000000000000000011621362265074000217170ustar00rootroot00000000000000#pragma once #include #include #include "location.hh" #include #include #include #include namespace rumur { struct Property : public Node { enum Category { ASSERTION, ASSUMPTION, COVER, LIVENESS, }; Category category; Ptr expr; Property(Category category_, const Ptr &expr_, const location &loc_); Property *clone() const final; virtual ~Property() = default; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; }; } rumur-2020.02.17/librumur/include/rumur/Ptr.h000066400000000000000000000037421362265074000206460ustar00rootroot00000000000000#pragma once #include #include #include #include namespace rumur { // An implementation of a managed pointer that understands *::clone() template class Ptr { private: std::unique_ptr t = std::unique_ptr(nullptr); public: Ptr() = default; Ptr(std::nullptr_t) { } explicit Ptr(TARGET *t_) : t(t_) { } Ptr(const Ptr &p) : t(p.t == nullptr ? nullptr : p.t->clone()) { } Ptr(Ptr &&p) noexcept { using std::swap; swap(t, p.t); } template Ptr(const Ptr &p) : t(p.get() == nullptr ? nullptr : p->clone()) { } Ptr &operator=(const Ptr &p) { t.reset(p.t == nullptr ? nullptr : p.t->clone()); return *this; } Ptr &operator=(Ptr &&p) noexcept { using std::swap; swap(t, p.t); return *this; } template Ptr &operator=(const Ptr &p) { t.reset(p.get() == nullptr ? nullptr : p->clone()); return *this; } const TARGET *get() const { return t.get(); } TARGET *get() { return t.get(); } TARGET &operator*() { assert(t != nullptr && "dereferencing a null pointer"); return *t; } const TARGET &operator*() const { assert(t != nullptr && "dereferencing a null pointer"); return *t; } TARGET *operator->() { assert(t != nullptr && "dereferencing a null pointer"); return t.get(); } const TARGET *operator->() const { assert(t != nullptr && "dereferencing a null pointer"); return t.get(); } bool operator==(const Ptr &other) const { return t == other.t; } bool operator==(const TARGET *other) const { return t.get() == other; } bool operator!=(const Ptr &other) const { return t != other.t; } bool operator!=(const TARGET *other) const { return t.get() != other; } template static Ptr make(Args&&... args) { return Ptr(new TARGET(std::forward(args)...)); } }; } rumur-2020.02.17/librumur/include/rumur/Rule.h000066400000000000000000000055141362265074000210070ustar00rootroot00000000000000#pragma once #include #include #include "location.hh" #include #include #include #include #include #include #include #include #include namespace rumur { struct Rule : public Node { std::string name; std::vector quantifiers; std::vector> aliases; Rule(const std::string &name_, const location &loc_); Rule *clone() const override = 0; virtual std::vector> flatten() const; virtual ~Rule() = default; }; struct AliasRule : public Rule { std::vector> rules; AliasRule(const std::vector> &aliases_, const std::vector> &rules_, const location &loc_); virtual ~AliasRule() = default; AliasRule *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::vector> flatten() const final; }; struct SimpleRule : public Rule { Ptr guard; std::vector> decls; std::vector> body; SimpleRule(const std::string &name_, const Ptr &guard_, const std::vector> &decls_, const std::vector> &body_, const location &loc_); virtual ~SimpleRule() = default; SimpleRule *clone() const override; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const override; void validate() const final; }; struct StartState : public Rule { std::vector> decls; std::vector> body; StartState(const std::string &name_, const std::vector> &decls_, const std::vector> &body_, const location &loc_); virtual ~StartState() = default; StartState *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; }; struct PropertyRule : public Rule { Property property; PropertyRule(const std::string &name_, const Property &property_, const location &loc_); virtual ~PropertyRule() = default; PropertyRule *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; }; struct Ruleset : public Rule { std::vector> rules; Ruleset(const std::vector &quantifiers_, const std::vector> &rules_, const location &loc_); virtual ~Ruleset() = default; Ruleset *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; std::vector> flatten() const final; }; } rumur-2020.02.17/librumur/include/rumur/Stmt.h000066400000000000000000000137641362265074000210350ustar00rootroot00000000000000#pragma once #include #include #include "location.hh" #include #include #include #include #include #include #include #include namespace rumur { struct Stmt : public Node { Stmt(const location &loc_); virtual ~Stmt() = default; virtual Stmt *clone() const = 0; }; struct AliasStmt : public Stmt { std::vector> aliases; std::vector> body; AliasStmt(const std::vector> &aliases_, const std::vector> &body_, const location &loc_); AliasStmt *clone() const final; virtual ~AliasStmt() = default; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; }; struct PropertyStmt : public Stmt { Property property; std::string message; PropertyStmt(const Property &property_, const std::string &message_, const location &loc_); PropertyStmt *clone() const final; virtual ~PropertyStmt() = default; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; }; struct Assignment : public Stmt { Ptr lhs; Ptr rhs; Assignment(const Ptr &lhs_, const Ptr &rhs_, const location &loc_); Assignment *clone() const final; virtual ~Assignment() = default; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; }; struct Clear : public Stmt { Ptr rhs; Clear(const Ptr &rhs_, const location &loc); virtual ~Clear() = default; Clear *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; }; struct ErrorStmt : public Stmt { std::string message; ErrorStmt(const std::string &message_, const location &loc_); ErrorStmt *clone() const final; virtual ~ErrorStmt() = default; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; }; struct For : public Stmt { Quantifier quantifier; std::vector> body; For(const Quantifier &quantifier_, const std::vector> &body_, const location &loc_); virtual ~For() = default; For *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; }; struct IfClause : public Node { Ptr condition; std::vector> body; IfClause(const Ptr &condition_, const std::vector> &body_, const location &loc_); virtual ~IfClause() = default; IfClause *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; }; struct If : public Stmt { std::vector clauses; If(const std::vector &clauses_, const location &loc_); virtual ~If() = default; If *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; }; struct ProcedureCall : public Stmt { FunctionCall call; ProcedureCall(const std::string &name, const std::vector> &arguments, const location &loc_); virtual ~ProcedureCall() = default; ProcedureCall *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; }; struct Put : public Stmt { std::string value; Ptr expr; Put(const std::string &value_, const location &loc_); Put(const Ptr &expr_, const location &loc_); virtual ~Put() = default; Put *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; }; struct Return : public Stmt { Ptr expr; Return(const Ptr &expr_, const location &loc_); virtual ~Return() = default; Return *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; }; struct SwitchCase : public Node { std::vector> matches; std::vector> body; SwitchCase(const std::vector> &matches_, const std::vector> &body_, const location &loc_); virtual ~SwitchCase() = default; SwitchCase *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; }; struct Switch : public Stmt { Ptr expr; std::vector cases; Switch(const Ptr &expr_, const std::vector &cases_, const location &loc_); virtual ~Switch() = default; Switch *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; }; struct Undefine : public Stmt { Ptr rhs; Undefine(const Ptr &rhs_, const location &loc_); virtual ~Undefine() = default; Undefine *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; }; struct While : public Stmt { Ptr condition; std::vector> body; While(const Ptr &condition_, const std::vector> &body_, const location &loc_); virtual ~While() = default; While *clone() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; }; } rumur-2020.02.17/librumur/include/rumur/Symtab.h000066400000000000000000000025301362265074000213320ustar00rootroot00000000000000#pragma once #include #include #include "location.hh" #include #include #include #include #include #include #include namespace rumur { class Symtab { private: std::vector>> scope; public: void open_scope() { scope.emplace_back(); } void close_scope() { assert(!scope.empty()); scope.pop_back(); } void declare(const std::string &name, const Ptr &value) { assert(!scope.empty()); if (scope.back().count(name) > 0) throw Error("symbol \"" + name + "\" was previously declared", value->loc); scope.back()[name] = value; } template Ptr lookup(const std::string &name, const location &loc) const { for (auto it = scope.rbegin(); it != scope.rend(); it++) { auto it2 = it->find(name); if (it2 != it->end()) { if (auto ret = dynamic_cast(it2->second.get())) { return Ptr(ret->clone()); } else { break; } } } throw Error("unknown symbol: " + name, loc); } // Whether we are in the top-level scope. __attribute__((deprecated("this will be removed in a future release"))) bool is_global_scope() const { return scope.size() == 1; } }; } rumur-2020.02.17/librumur/include/rumur/TypeExpr.h000066400000000000000000000124561362265074000216630ustar00rootroot00000000000000#pragma once #include #include #include #include #include "location.hh" #include #include #include #include #include #include #include namespace rumur { // Forward declare to avoid circular #include struct TypeDecl; struct VarDecl; struct TypeExpr : public Node { TypeExpr(const location &loc_); virtual ~TypeExpr() = default; // Whether this type is a primitive integer-like type. virtual bool is_simple() const; TypeExpr *clone() const override = 0; virtual mpz_class width() const; virtual mpz_class count() const = 0; virtual Ptr resolve() const; /* Numeric bounds of this type as valid C code. These are only valid to use on * TypeExprs for which is_simple() returns true. */ virtual std::string lower_bound() const; virtual std::string upper_bound() const; // Get a string representation of this type virtual std::string to_string() const = 0; /* Whether this type's bounds are constant. Only valid for TypeExprs for which * is_simple() returns true. */ virtual bool constant() const; // can a value of this type can be assigned to or compared with a value of the // given type? bool coerces_to(const TypeExpr &other) const; // Is this the type Boolean? Note that this only returns true for the actual // type Boolean, and not for TypeExprIDs that point at Boolean. virtual bool is_boolean() const; __attribute__((deprecated("equatable_with() has been replaced by coerces_to()"))) bool equatable_with(const TypeExpr &other) const; }; struct Range : public TypeExpr { Ptr min; Ptr max; Range(const Ptr &min_, const Ptr &max_, const location &loc_); Range *clone() const final; virtual ~Range() = default; mpz_class count() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; bool is_simple() const final; void validate() const final; std::string lower_bound() const final; std::string upper_bound() const final; std::string to_string() const final; bool constant() const final; }; struct Scalarset : public TypeExpr { Ptr bound; Scalarset(const Ptr &bound_, const location &loc_); Scalarset *clone() const final; virtual ~Scalarset() = default; mpz_class count() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; bool is_simple() const final; void validate() const final; std::string lower_bound() const final; std::string upper_bound() const final; std::string to_string() const final; bool constant() const final; }; struct Enum : public TypeExpr { std::vector> members; // The range [unique_id, unique_id_limit) is usable by this node as // identifiers. Enum types need this specialisation due to the way references // to their members are resolved (see resolve_symbols()). size_t unique_id_limit = SIZE_MAX; Enum(const std::vector> &members_, const location &loc_); Enum *clone() const final; virtual ~Enum() = default; mpz_class count() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; bool is_simple() const final; void validate() const final; std::string lower_bound() const final; std::string upper_bound() const final; std::string to_string() const final; bool constant() const final; bool is_boolean() const final; }; struct Record : public TypeExpr { std::vector> fields; Record(const std::vector> &fields_, const location &loc_); Record *clone() const final; virtual ~Record() = default; mpz_class width() const final; mpz_class count() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; std::string to_string() const final; }; struct Array : public TypeExpr { Ptr index_type; Ptr element_type; Array(const Ptr &index_type_, const Ptr &element_type_, const location &loc_); Array *clone() const final; virtual ~Array() = default; mpz_class width() const final; mpz_class count() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; void validate() const final; std::string to_string() const final; }; struct TypeExprID : public TypeExpr { std::string name; Ptr referent; TypeExprID(const std::string &name_, const Ptr &referent_, const location &loc_); TypeExprID *clone() const final; virtual ~TypeExprID() = default; mpz_class width() const final; mpz_class count() const final; // __attribute__((deprecated("operator== will be removed in a future release"))) bool operator==(const Node &other) const final; bool is_simple() const final; Ptr resolve() const final; void validate() const final; std::string lower_bound() const final; std::string upper_bound() const final; std::string to_string() const final; bool constant() const final; }; } rumur-2020.02.17/librumur/include/rumur/except.h000066400000000000000000000006151362265074000213650ustar00rootroot00000000000000#pragma once #include #include "location.hh" #include #include namespace rumur { /* A basic exception to allow us to easily catch only the errors thrown by * ourselves. */ class Error : public std::runtime_error { public: location loc; Error(const std::string &message, const location &loc_); Error(const std::string &prefix, const Error &sub); }; } rumur-2020.02.17/librumur/include/rumur/indexer.h000066400000000000000000000053021362265074000215310ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include namespace rumur { class Indexer : public BaseTraversal { private: size_t next = 0; public: void visit_add(Add &n) final; void visit_aliasdecl(AliasDecl &n) final; void visit_aliasrule(AliasRule &n) final; void visit_aliasstmt(AliasStmt &n) final; void visit_and(And &n) final; void visit_array(Array &n) final; void visit_assignment(Assignment &n) final; void visit_clear(Clear &n) final; void visit_constdecl(ConstDecl &n) final; void visit_div(Div &n) final; void visit_element(Element &n) final; void visit_enum(Enum &n) final; void visit_eq(Eq &n) final; void visit_errorstmt(ErrorStmt &n) final; void visit_exists(Exists &n) final; void visit_exprid(ExprID &n) final; void visit_field(Field &n) final; void visit_for(For &n) final; void visit_forall(Forall &n) final; void visit_function(Function &n) final; void visit_functioncall(FunctionCall &n) final; void visit_geq(Geq &n) final; void visit_gt(Gt &n) final; void visit_if(If &n) final; void visit_ifclause(IfClause &n) final; void visit_implication(Implication &n) final; void visit_isundefined(IsUndefined &n) final; void visit_leq(Leq &n) final; void visit_lt(Lt &n) final; void visit_model(Model &n) final; void visit_mod(Mod &n) final; void visit_mul(Mul &n) final; void visit_negative(Negative &n) final; void visit_neq(Neq &n) final; void visit_not(Not &n) final; void visit_number(Number &n) final; void visit_or(Or &n) final; void visit_procedurecall(ProcedureCall &n) final; void visit_property(Property &n) final; void visit_propertyrule(PropertyRule &n) final; void visit_propertystmt(PropertyStmt &n) final; void visit_put(Put &n) final; void visit_quantifier(Quantifier &n) final; void visit_range(Range &n) final; void visit_record(Record &n) final; void visit_return(Return &n) final; void visit_ruleset(Ruleset &n) final; void visit_scalarset(Scalarset &n) final; void visit_simplerule(SimpleRule &n) final; void visit_startstate(StartState &n) final; void visit_sub(Sub &n) final; void visit_switch(Switch &n) final; void visit_switchcase(SwitchCase &n) final; void visit_ternary(Ternary &n) final; void visit_typedecl(TypeDecl &n) final; void visit_typeexprid(TypeExprID &n) final; void visit_undefine(Undefine &n) final; void visit_vardecl(VarDecl &n) final; void visit_while(While &n) final; virtual ~Indexer() = default; private: void visit_bexpr(BinaryExpr &n); void visit_uexpr(UnaryExpr &n); }; } rumur-2020.02.17/librumur/include/rumur/parse.h000066400000000000000000000005641362265074000212120ustar00rootroot00000000000000#pragma once #include #include #include #include namespace rumur { // Parse in a model from an input stream. Throws Errors on parsing errors. Ptr parse(std::istream &input); Ptr parse(std::istream *input) __attribute__((deprecated("parse() should be called with a reference instead of a pointer"))); } rumur-2020.02.17/librumur/include/rumur/resolve-symbols.h000066400000000000000000000003721362265074000232420ustar00rootroot00000000000000#pragma once #include #include namespace rumur { /* Resolve symbolic references (rumur::ExprIDs and rumur::TypeExprIDs) within a * model. Throws rumur::Error if this process fails. */ void resolve_symbols(Model &m); } rumur-2020.02.17/librumur/include/rumur/rumur.h000066400000000000000000000013761362265074000212540ustar00rootroot00000000000000// A header for lazy folks who just want all of librumur's functionality. #pragma once #include "location.hh" #include "parser.yy.hh" #include "position.hh" #include #include #include #include #include #include "rumur-get-version.h" // generated #include #include #include #include #include #include #include #include #include #include #include #include #include #include // stack.hh is deliberately not included; just use std::stack rumur-2020.02.17/librumur/include/rumur/scanner.h000066400000000000000000000016561362265074000215340ustar00rootroot00000000000000#pragma once #include #include #ifndef yyFlexLexerOnce #include #endif #include "parser.yy.hh" namespace rumur { class scanner : public yyFlexLexer { public: // Delegate to yyFlexLexer's constructor scanner(std::istream *arg_yyin = 0, std::ostream *arg_yyout = 0): yyFlexLexer(arg_yyin, arg_yyout) { } /* XXX: Clang's -Woverloaded-virtual decides that the following declaration is * possibly a mistake. However, we are deliberately overloading this method with * a different type signature. The cleanest way I've found around it is to * toggle off the warning here. */ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Woverloaded-virtual" #endif // Force a new available type signature for yylex virtual int yylex(parser::semantic_type *const lval, parser::location_type *loc); #ifdef __clang__ #pragma clang diagnostic pop #endif }; } rumur-2020.02.17/librumur/include/rumur/traverse.h000066400000000000000000000460561362265074000217410ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace rumur { /* Generic abstract syntax tree traversal interface with no implementation. If * you want to implement a traversal that handles every Node type you should * define a class that inherits from this. If you want a more liberal base class * that provides default implementations for the 'visit' methods you don't need * to override, inherit from Traversal below. */ class BaseTraversal { public: virtual void visit_add(Add &n) = 0; virtual void visit_aliasdecl(AliasDecl &n) = 0; virtual void visit_aliasrule(AliasRule &n) = 0; virtual void visit_aliasstmt(AliasStmt &n) = 0; virtual void visit_and(And &n) = 0; virtual void visit_array(Array &n) = 0; virtual void visit_assignment(Assignment &n) = 0; virtual void visit_clear(Clear &n) = 0; virtual void visit_constdecl(ConstDecl &n) = 0; virtual void visit_div(Div &n) = 0; virtual void visit_element(Element &n) = 0; virtual void visit_enum(Enum &n) = 0; virtual void visit_eq(Eq &n) = 0; virtual void visit_errorstmt(ErrorStmt &n) = 0; virtual void visit_exists(Exists &n) = 0; virtual void visit_exprid(ExprID &n) = 0; virtual void visit_field(Field &n) = 0; virtual void visit_for(For &n) = 0; virtual void visit_forall(Forall &n) = 0; virtual void visit_function(Function &n) = 0; virtual void visit_functioncall(FunctionCall &n) = 0; virtual void visit_geq(Geq &n) = 0; virtual void visit_gt(Gt &n) = 0; virtual void visit_if(If &n) = 0; virtual void visit_ifclause(IfClause &n) = 0; virtual void visit_implication(Implication &n) = 0; virtual void visit_isundefined(IsUndefined &n) = 0; virtual void visit_leq(Leq &n) = 0; virtual void visit_lt(Lt &n) = 0; virtual void visit_model(Model &n) = 0; virtual void visit_mod(Mod &n) = 0; virtual void visit_mul(Mul &n) = 0; virtual void visit_negative(Negative &n) = 0; virtual void visit_neq(Neq &n) = 0; virtual void visit_not(Not &n) = 0; virtual void visit_number(Number &n) = 0; virtual void visit_or(Or &n) = 0; virtual void visit_procedurecall(ProcedureCall &n) = 0; virtual void visit_property(Property &n) = 0; virtual void visit_propertyrule(PropertyRule &n) = 0; virtual void visit_propertystmt(PropertyStmt &n) = 0; virtual void visit_put(Put &n) = 0; virtual void visit_quantifier(Quantifier &n) = 0; virtual void visit_range(Range &n) = 0; virtual void visit_record(Record &n) = 0; virtual void visit_return(Return &n) = 0; virtual void visit_ruleset(Ruleset &n) = 0; virtual void visit_scalarset(Scalarset &n) = 0; virtual void visit_simplerule(SimpleRule &n) = 0; virtual void visit_startstate(StartState &n) = 0; virtual void visit_sub(Sub &n) = 0; virtual void visit_switch(Switch &n) = 0; virtual void visit_switchcase(SwitchCase &n) = 0; virtual void visit_ternary(Ternary &n) = 0; virtual void visit_typedecl(TypeDecl &n) = 0; virtual void visit_typeexprid(TypeExprID &n) = 0; virtual void visit_undefine(Undefine &n) = 0; virtual void visit_vardecl(VarDecl &n) = 0; virtual void visit_while(While &n) = 0; /* Visitation dispatch. This simply determines the type of the Node argument * and calls the appropriate specialised 'visit' method. This is not virtual * because we do not anticipate use cases where this behaviour needs to * change. */ void dispatch(Node &n); virtual ~BaseTraversal() = default; }; class Traversal : public BaseTraversal { public: void visit_add(Add &n) override; void visit_aliasdecl(AliasDecl &n) override; void visit_aliasrule(AliasRule &n) override; void visit_aliasstmt(AliasStmt &n) override; void visit_and(And &n) override; void visit_array(Array &n) override; void visit_assignment(Assignment &n) override; void visit_clear(Clear &n) override; void visit_constdecl(ConstDecl &n) override; void visit_div(Div &n) override; void visit_element(Element &n) override; void visit_enum(Enum &n) override; void visit_eq(Eq &n) override; void visit_errorstmt(ErrorStmt &n) override; void visit_exists(Exists &n) override; void visit_exprid(ExprID &n) override; void visit_field(Field &n) override; void visit_for(For &n) override; void visit_forall(Forall &n) override; void visit_function(Function &n) override; void visit_functioncall(FunctionCall &n) override; void visit_geq(Geq &n) override; void visit_gt(Gt &n) override; void visit_if(If &n) override; void visit_ifclause(IfClause &n) override; void visit_implication(Implication &n) override; void visit_isundefined(IsUndefined &n) override; void visit_leq(Leq &n) override; void visit_lt(Lt &n) override; void visit_model(Model &n) override; void visit_mod(Mod &n) override; void visit_mul(Mul &n) override; void visit_negative(Negative &n) override; void visit_neq(Neq &n) override; void visit_not(Not &n) override; void visit_number(Number &n) override; void visit_or(Or &n) override; void visit_procedurecall(ProcedureCall &n) override; void visit_property(Property &n) override; void visit_propertyrule(PropertyRule &n) override; void visit_propertystmt(PropertyStmt &n) override; void visit_put(Put &n) override; void visit_quantifier(Quantifier &n) override; void visit_range(Range &n) override; void visit_record(Record &n) override; void visit_return(Return &n) override; void visit_ruleset(Ruleset &n) override; void visit_scalarset(Scalarset &n) override; void visit_simplerule(SimpleRule &n) override; void visit_startstate(StartState &n) override; void visit_sub(Sub &n) override; void visit_switch(Switch &n) override; void visit_switchcase(SwitchCase &n) override; void visit_ternary(Ternary &n) override; void visit_typedecl(TypeDecl &n) override; void visit_typeexprid(TypeExprID &n) override; void visit_undefine(Undefine &n) override; void visit_vardecl(VarDecl &n) override; void visit_while(While &n) override; // Force class to be abstract virtual ~Traversal() = 0; private: void visit_bexpr(BinaryExpr &n); void visit_uexpr(UnaryExpr &n); }; // Read-only equivalent of BaseTraversal. class ConstBaseTraversal { public: virtual void visit_add(const Add &n) = 0; virtual void visit_aliasdecl(const AliasDecl &n) = 0; virtual void visit_aliasrule(const AliasRule &n) = 0; virtual void visit_aliasstmt(const AliasStmt &n) = 0; virtual void visit_and(const And &n) = 0; virtual void visit_array(const Array &n) = 0; virtual void visit_assignment(const Assignment &n) = 0; virtual void visit_clear(const Clear &n) = 0; virtual void visit_constdecl(const ConstDecl &n) = 0; virtual void visit_div(const Div &n) = 0; virtual void visit_element(const Element &n) = 0; virtual void visit_enum(const Enum &n) = 0; virtual void visit_eq(const Eq &n) = 0; virtual void visit_errorstmt(const ErrorStmt &n) = 0; virtual void visit_exists(const Exists &n) = 0; virtual void visit_exprid(const ExprID &n) = 0; virtual void visit_field(const Field &n) = 0; virtual void visit_for(const For &n) = 0; virtual void visit_forall(const Forall &n) = 0; virtual void visit_function(const Function &n) = 0; virtual void visit_functioncall(const FunctionCall &n) = 0; virtual void visit_geq(const Geq &n) = 0; virtual void visit_gt(const Gt &n) = 0; virtual void visit_if(const If &n) = 0; virtual void visit_ifclause(const IfClause &n) = 0; virtual void visit_implication(const Implication &n) = 0; virtual void visit_isundefined(const IsUndefined &n) = 0; virtual void visit_leq(const Leq &n) = 0; virtual void visit_lt(const Lt &n) = 0; virtual void visit_model(const Model &n) = 0; virtual void visit_mod(const Mod &n) = 0; virtual void visit_mul(const Mul &n) = 0; virtual void visit_negative(const Negative &n) = 0; virtual void visit_neq(const Neq &n) = 0; virtual void visit_not(const Not &n) = 0; virtual void visit_number(const Number &n) = 0; virtual void visit_or(const Or &n) = 0; virtual void visit_procedurecall(const ProcedureCall &n) = 0; virtual void visit_property(const Property &n) = 0; virtual void visit_propertyrule(const PropertyRule &n) = 0; virtual void visit_propertystmt(const PropertyStmt &n) = 0; virtual void visit_put(const Put &n) = 0; virtual void visit_quantifier(const Quantifier &n) = 0; virtual void visit_range(const Range &n) = 0; virtual void visit_record(const Record &n) = 0; virtual void visit_return(const Return &n) = 0; virtual void visit_ruleset(const Ruleset &n) = 0; virtual void visit_scalarset(const Scalarset &n) = 0; virtual void visit_simplerule(const SimpleRule &n) = 0; virtual void visit_startstate(const StartState &n) = 0; virtual void visit_sub(const Sub &n) = 0; virtual void visit_switch(const Switch &n) = 0; virtual void visit_switchcase(const SwitchCase &n) = 0; virtual void visit_ternary(const Ternary &n) = 0; virtual void visit_typedecl(const TypeDecl &n) = 0; virtual void visit_typeexprid(const TypeExprID &n) = 0; virtual void visit_undefine(const Undefine &n) = 0; virtual void visit_vardecl(const VarDecl &n) = 0; virtual void visit_while(const While &n) = 0; void dispatch(const Node &n); virtual ~ConstBaseTraversal() = default; }; // Read-only equivalent of Traversal. class ConstTraversal : public ConstBaseTraversal { public: void visit_add(const Add &n) override; void visit_aliasdecl(const AliasDecl &n) override; void visit_aliasrule(const AliasRule &n) override; void visit_aliasstmt(const AliasStmt &n) override; void visit_and(const And &n) override; void visit_array(const Array &n) override; void visit_assignment(const Assignment &n) override; void visit_clear(const Clear &n) override; void visit_constdecl(const ConstDecl &n) override; void visit_div(const Div &n) override; void visit_element(const Element &n) override; void visit_enum(const Enum &n) override; void visit_eq(const Eq &n) override; void visit_errorstmt(const ErrorStmt &n) override; void visit_exists(const Exists &n) override; void visit_exprid(const ExprID &n) override; void visit_field(const Field &n) override; void visit_for(const For &n) override; void visit_forall(const Forall &n) override; void visit_function(const Function &n) override; void visit_functioncall(const FunctionCall &n) override; void visit_geq(const Geq &n) override; void visit_gt(const Gt &n) override; void visit_if(const If &n) override; void visit_ifclause(const IfClause &n) override; void visit_implication(const Implication &n) override; void visit_isundefined(const IsUndefined &n) override; void visit_leq(const Leq &n) override; void visit_lt(const Lt &n) override; void visit_model(const Model &n) override; void visit_mod(const Mod &n) override; void visit_mul(const Mul &n) override; void visit_negative(const Negative &n) override; void visit_neq(const Neq &n) override; void visit_not(const Not &n) override; void visit_number(const Number &n) override; void visit_or(const Or &n) override; void visit_procedurecall(const ProcedureCall &n) override; void visit_property(const Property &n) override; void visit_propertyrule(const PropertyRule &n) override; void visit_propertystmt(const PropertyStmt &n) override; void visit_put(const Put &n) override; void visit_quantifier(const Quantifier &n) override; void visit_range(const Range &n) override; void visit_record(const Record &n) override; void visit_return(const Return &n) override; void visit_ruleset(const Ruleset &n) override; void visit_scalarset(const Scalarset &n) override; void visit_simplerule(const SimpleRule &n) override; void visit_startstate(const StartState &n) override; void visit_sub(const Sub &n) override; void visit_switch(const Switch &n) override; void visit_switchcase(const SwitchCase &n) override; void visit_ternary(const Ternary &n) override; void visit_typedecl(const TypeDecl &n) override; void visit_typeexprid(const TypeExprID &n) override; void visit_undefine(const Undefine &n) override; void visit_vardecl(const VarDecl &n) override; void visit_while(const While &n) override; // Force class to be abstract virtual ~ConstTraversal() = 0; private: void visit_bexpr(const BinaryExpr &n); void visit_uexpr(const UnaryExpr &n); }; /* Generic base for read-only traversals that only need to act on expressions. * This gives you a default implementation for visitation of any non-expression * node. */ class ConstExprTraversal : public ConstBaseTraversal { public: void visit_aliasdecl(const AliasDecl &n) final; void visit_aliasrule(const AliasRule &n) final; void visit_aliasstmt(const AliasStmt &n) final; void visit_array(const Array &n) final; void visit_assignment(const Assignment &n) final; void visit_clear(const Clear &n) final; void visit_constdecl(const ConstDecl &n) final; void visit_enum(const Enum &n) final; void visit_errorstmt(const ErrorStmt &n) final; void visit_for(const For &n) final; void visit_function(const Function &n) final; void visit_if(const If &n) final; void visit_ifclause(const IfClause &n) final; void visit_model(const Model &n) final; void visit_procedurecall(const ProcedureCall &n) final; void visit_property(const Property &n) final; void visit_propertyrule(const PropertyRule &n) final; void visit_propertystmt(const PropertyStmt &n) final; void visit_put(const Put &n) final; void visit_quantifier(const Quantifier &n) final; void visit_range(const Range &n) final; void visit_record(const Record &n) final; void visit_return(const Return &n) final; void visit_ruleset(const Ruleset &n) final; void visit_scalarset(const Scalarset &n) final; void visit_simplerule(const SimpleRule &n) final; void visit_startstate(const StartState &n) final; void visit_switch(const Switch &n) final; void visit_switchcase(const SwitchCase &n) final; void visit_typedecl(const TypeDecl &n) final; void visit_typeexprid(const TypeExprID &n) final; void visit_undefine(const Undefine &n) final; void visit_vardecl(const VarDecl &n) final; void visit_while(const While &n) final; virtual ~ConstExprTraversal() = default; }; /* Generic base for read-only traversals that only need to act on statements. * This gives you a default implementation for visitation of any non-statement * node. */ class ConstStmtTraversal : public ConstBaseTraversal { public: void visit_add(const Add &n) final; void visit_aliasdecl(const AliasDecl &n) final; void visit_aliasrule(const AliasRule &n) final; void visit_and(const And &n) final; void visit_array(const Array &n) final; void visit_constdecl(const ConstDecl &n) final; void visit_div(const Div &n) final; void visit_element(const Element &n) final; void visit_enum(const Enum &n) final; void visit_eq(const Eq &n) final; void visit_exists(const Exists &n) final; void visit_exprid(const ExprID &n) final; void visit_field(const Field &n) final; void visit_forall(const Forall &n) final; void visit_function(const Function &n) final; void visit_functioncall(const FunctionCall &n) final; void visit_geq(const Geq &n) final; void visit_gt(const Gt &n) final; void visit_ifclause(const IfClause &n) final; void visit_implication(const Implication &n) final; void visit_isundefined(const IsUndefined &n) final; void visit_leq(const Leq &n) final; void visit_lt(const Lt &n) final; void visit_model(const Model &n) final; void visit_mod(const Mod &n) final; void visit_mul(const Mul &n) final; void visit_negative(const Negative &n) final; void visit_neq(const Neq &n) final; void visit_not(const Not &n) final; void visit_number(const Number &n) final; void visit_or(const Or &n) final; void visit_property(const Property &n) final; void visit_propertyrule(const PropertyRule &n) final; void visit_quantifier(const Quantifier &n) final; void visit_range(const Range &n) final; void visit_record(const Record &n) final; void visit_ruleset(const Ruleset &n) final; void visit_scalarset(const Scalarset &n) final; void visit_simplerule(const SimpleRule &n) final; void visit_startstate(const StartState &n) final; void visit_sub(const Sub &n) final; void visit_switchcase(const SwitchCase &n) final; void visit_ternary(const Ternary &n) final; void visit_typedecl(const TypeDecl &n) final; void visit_typeexprid(const TypeExprID &n) final; void visit_vardecl(const VarDecl &n) final; virtual ~ConstStmtTraversal() = default; private: void visit_bexpr(const BinaryExpr &n); void visit_uexpr(const UnaryExpr &n); }; // Generic base for read-only traversals that only need to act on TypeExprs class ConstTypeTraversal : public ConstBaseTraversal { public: void visit_add(const Add &n) final; void visit_aliasdecl(const AliasDecl &n) final; void visit_aliasrule(const AliasRule &n) final; void visit_aliasstmt(const AliasStmt &n) final; void visit_and(const And &n) final; void visit_assignment(const Assignment &n) final; void visit_clear(const Clear &n) final; void visit_constdecl(const ConstDecl &n) final; void visit_div(const Div &n) final; void visit_element(const Element &n) final; void visit_eq(const Eq &n) final; void visit_errorstmt(const ErrorStmt &n) final; void visit_exists(const Exists &n) final; void visit_exprid(const ExprID &n) final; void visit_field(const Field &n) final; void visit_for(const For &n) final; void visit_forall(const Forall &n) final; void visit_function(const Function &n) final; void visit_functioncall(const FunctionCall &n) final; void visit_geq(const Geq &n) final; void visit_gt(const Gt &n) final; void visit_if(const If &n) final; void visit_ifclause(const IfClause &n) final; void visit_implication(const Implication &n) final; void visit_isundefined(const IsUndefined &n) final; void visit_leq(const Leq &n) final; void visit_lt(const Lt &n) final; void visit_model(const Model &n) final; void visit_mod(const Mod &n) final; void visit_mul(const Mul &n) final; void visit_negative(const Negative &n) final; void visit_neq(const Neq &n) final; void visit_not(const Not &n) final; void visit_number(const Number &n) final; void visit_or(const Or &n) final; void visit_procedurecall(const ProcedureCall &n) final; void visit_property(const Property &n) final; void visit_propertyrule(const PropertyRule &n) final; void visit_propertystmt(const PropertyStmt &n) final; void visit_put(const Put &n) final; void visit_quantifier(const Quantifier &n) final; void visit_return(const Return &n) final; void visit_ruleset(const Ruleset &n) final; void visit_simplerule(const SimpleRule &n) final; void visit_startstate(const StartState &n) final; void visit_sub(const Sub &n) final; void visit_switch(const Switch &n) final; void visit_switchcase(const SwitchCase &n) final; void visit_ternary(const Ternary &n) final; void visit_typedecl(const TypeDecl &n) final; void visit_undefine(const Undefine &n) final; void visit_vardecl(const VarDecl &n) final; void visit_while(const While &n) final; virtual ~ConstTypeTraversal() = default; private: void visit_bexpr(const BinaryExpr &n); void visit_uexpr(const UnaryExpr &n); }; } rumur-2020.02.17/librumur/include/rumur/validate.h000066400000000000000000000003631362265074000216660ustar00rootroot00000000000000#pragma once #include #include #include namespace rumur { /* Check a node in the AST and all its children for inconsistencies and throw * rumur::Error if found. */ void validate(const Node &n); } rumur-2020.02.17/librumur/src/000077500000000000000000000000001362265074000157145ustar00rootroot00000000000000rumur-2020.02.17/librumur/src/Boolean.cc000066400000000000000000000014011362265074000175760ustar00rootroot00000000000000#include #include "location.hh" #include #include #include #include #include #include #include #include #include #include namespace rumur { const Ptr Boolean = Ptr::make( std::vector>( { {"false", location()}, {"true", location()} }), location()); const Ptr False = Ptr::make("false", Ptr::make("boolean", Ptr::make(0, location()), Boolean, location()), location()); const Ptr True = Ptr::make("true", Ptr::make("boolean", Ptr::make(1, location()), Boolean, location()), location()); } rumur-2020.02.17/librumur/src/Decl.cc000066400000000000000000000074241362265074000171010ustar00rootroot00000000000000#include #include #include #include "location.hh" #include #include #include #include #include #include #include namespace rumur { Decl::Decl(const std::string &name_, const location &loc_): Node(loc_), name(name_) { } Decl::~Decl() { } ExprDecl::ExprDecl(const std::string &name_, const location &loc_): Decl(name_, loc_) { } AliasDecl::AliasDecl(const std::string &name_, const Ptr &value_, const location &loc_): ExprDecl(name_, loc_), value(value_) { } AliasDecl *AliasDecl::clone() const { return new AliasDecl(*this); } bool AliasDecl::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (name != o->name) return false; if (*value != *o->value) return false; return true; } bool AliasDecl::is_lvalue() const { return value->is_lvalue(); } bool AliasDecl::is_readonly() const { return value->is_readonly(); } Ptr AliasDecl::get_type() const { return value->type(); } ConstDecl::ConstDecl(const std::string &name_, const Ptr &value_, const location &loc_): ExprDecl(name_, loc_), value(value_) { } ConstDecl::ConstDecl(const std::string &name_, const Ptr &value_, const Ptr &type_, const location &loc_): ExprDecl(name_, loc_), value(value_), type(type_) { } ConstDecl *ConstDecl::clone() const { return new ConstDecl(*this); } bool ConstDecl::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (name != o->name) return false; if (*value != *o->value) return false; if (type == nullptr) { if (o->type != nullptr) return false; } else { if (o->type == nullptr) return false; if (*type != *o->type) return false; } return true; } bool ConstDecl::is_lvalue() const { return false; } bool ConstDecl::is_readonly() const { return true; } Ptr ConstDecl::get_type() const { // If this constant has an explicit type (e.g. it's an enum member), use that. if (type != nullptr) return type; /* If this doesn't have an explicit type, fall back on the type of the value * it points at. This is irrelevant for numerical constants, but important for * boolean constants. */ return value->type(); } void ConstDecl::validate() const { if (!value->constant()) throw Error("const definition is not a constant", value->loc); } TypeDecl::TypeDecl(const std::string &name_, const Ptr &value_, const location &loc_): Decl(name_, loc_), value(value_) { } TypeDecl *TypeDecl::clone() const { return new TypeDecl(*this); } bool TypeDecl::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && name == o->name && *value == *o->value; } VarDecl::VarDecl(const std::string &name_, const Ptr &type_, const location &loc_): ExprDecl(name_, loc_), type(type_) { } VarDecl *VarDecl::clone() const { return new VarDecl(*this); } bool VarDecl::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (name != o->name) return false; if (*type != *o->type) return false; if (offset != o->offset) return false; if (readonly != o->readonly) return false; return true; } bool VarDecl::is_lvalue() const { return true; } bool VarDecl::is_readonly() const { return readonly; } bool VarDecl::is_in_state() const { return offset >= 0; } mpz_class VarDecl::width() const { return type->width(); } mpz_class VarDecl::count() const { return type->count(); } Ptr VarDecl::get_type() const { return type; } } rumur-2020.02.17/librumur/src/Expr.cc000066400000000000000000000716641362265074000171570ustar00rootroot00000000000000#include #include #include #include #include #include #include "location.hh" #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include namespace rumur { Expr::Expr(const location &loc_): Node(loc_) { } bool Expr::is_boolean() const { return type()->resolve()->is_boolean(); } bool Expr::is_lvalue() const { return false; } bool Expr::is_readonly() const { return !is_lvalue(); } bool Expr::is_literal_true() const { return false; } bool Expr::is_literal_false() const { return false; } Ternary::Ternary(const Ptr &cond_, const Ptr &lhs_, const Ptr &rhs_, const location &loc_): Expr(loc_), cond(cond_), lhs(lhs_), rhs(rhs_) { } Ternary *Ternary::clone() const { return new Ternary(*this); } bool Ternary::constant() const { return cond->constant() && lhs->constant() && rhs->constant(); } bool Ternary::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *cond == *o->cond && *lhs == *o->lhs && *rhs == *o->rhs; } Ptr Ternary::type() const { // TODO: assert lhs and rhs are compatible types. return lhs->type(); } mpz_class Ternary::constant_fold() const { return cond->constant_fold() != 0 ? lhs->constant_fold() : rhs->constant_fold(); } void Ternary::validate() const { if (!cond->is_boolean()) throw Error("ternary condition is not a boolean", cond->loc); } std::string Ternary::to_string() const { return "(" + cond->to_string() + " ? " + lhs->to_string() + " : " + rhs->to_string() + ")"; } BinaryExpr::BinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): Expr(loc_), lhs(lhs_), rhs(rhs_) { } bool BinaryExpr::constant() const { return lhs->constant() && rhs->constant(); } BooleanBinaryExpr::BooleanBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): BinaryExpr(lhs_, rhs_, loc_) { } void BooleanBinaryExpr::validate() const { if (!lhs->is_boolean()) throw Error("left hand side of expression is not a boolean", lhs->loc); if (!rhs->is_boolean()) throw Error("right hand side of expression is not a boolean", rhs->loc); } Implication::Implication(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): BooleanBinaryExpr(lhs_, rhs_, loc_) { } Implication *Implication::clone() const { return new Implication(*this); } Ptr Implication::type() const { return Boolean; } mpz_class Implication::constant_fold() const { return lhs->constant_fold() == 0 || rhs->constant_fold() != 0; } bool Implication::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Implication::to_string() const { return "(" + lhs->to_string() + " -> " + rhs->to_string() + ")"; } Or::Or(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): BooleanBinaryExpr(lhs_, rhs_, loc_) { } Or *Or::clone() const { return new Or(*this); } Ptr Or::type() const { return Boolean; } mpz_class Or::constant_fold() const { return lhs->constant_fold() != 0 || rhs->constant_fold() != 0; } bool Or::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Or::to_string() const { return "(" + lhs->to_string() + " | " + rhs->to_string() + ")"; } And::And(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): BooleanBinaryExpr(lhs_, rhs_, loc_) { } And *And::clone() const { return new And(*this); } Ptr And::type() const { return Boolean; } mpz_class And::constant_fold() const { return lhs->constant_fold() != 0 && rhs->constant_fold() != 0; } bool And::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string And::to_string() const { return "(" + lhs->to_string() + " & " + rhs->to_string() + ")"; } UnaryExpr::UnaryExpr(const Ptr &rhs_, const location &loc_): Expr(loc_), rhs(rhs_) { } bool UnaryExpr::constant() const { return rhs->constant(); } Not::Not(const Ptr &rhs_, const location &loc_): UnaryExpr(rhs_, loc_) { } Not *Not::clone() const { return new Not(*this); } Ptr Not::type() const { return Boolean; } mpz_class Not::constant_fold() const { return rhs->constant_fold() == 0; } bool Not::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *rhs == *o->rhs; } void Not::validate() const { if (!rhs->is_boolean()) throw Error("argument to ! is not a boolean", rhs->loc); } std::string Not::to_string() const { return "(!" + rhs->to_string() + ")"; } static bool comparable(const Expr &lhs, const Expr &rhs) { const Ptr t1 = lhs.type()->resolve(); const Ptr t2 = rhs.type()->resolve(); if (isa(t1)) { if (isa(t2)) return true; } return false; } ComparisonBinaryExpr::ComparisonBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): BinaryExpr(lhs_, rhs_, loc_) { } void ComparisonBinaryExpr::validate() const { if (!comparable(*lhs, *rhs)) throw Error("expressions are not comparable", loc); } Lt::Lt(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): ComparisonBinaryExpr(lhs_, rhs_, loc_) { } Lt *Lt::clone() const { return new Lt(*this); } Ptr Lt::type() const { return Boolean; } mpz_class Lt::constant_fold() const { return lhs->constant_fold() < rhs->constant_fold(); } bool Lt::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Lt::to_string() const { return "(" + lhs->to_string() + " < " + rhs->to_string() + ")"; } Leq::Leq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): ComparisonBinaryExpr(lhs_, rhs_, loc_) { } Leq *Leq::clone() const { return new Leq(*this); } Ptr Leq::type() const { return Boolean; } mpz_class Leq::constant_fold() const { return lhs->constant_fold() <= rhs->constant_fold(); } bool Leq::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Leq::to_string() const { return "(" + lhs->to_string() + " <= " + rhs->to_string() + ")"; } Gt::Gt(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): ComparisonBinaryExpr(lhs_, rhs_, loc_) { } Gt *Gt::clone() const { return new Gt(*this); } Ptr Gt::type() const { return Boolean; } mpz_class Gt::constant_fold() const { return lhs->constant_fold() > rhs->constant_fold(); } bool Gt::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Gt::to_string() const { return "(" + lhs->to_string() + " > " + rhs->to_string() + ")"; } Geq::Geq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): ComparisonBinaryExpr(lhs_, rhs_, loc_) { } Geq *Geq::clone() const { return new Geq(*this); } Ptr Geq::type() const { return Boolean; } mpz_class Geq::constant_fold() const { return lhs->constant_fold() >= rhs->constant_fold(); } bool Geq::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Geq::to_string() const { return "(" + lhs->to_string() + " >= " + rhs->to_string() + ")"; } EquatableBinaryExpr::EquatableBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): BinaryExpr(lhs_, rhs_, loc_) { } void EquatableBinaryExpr::validate() const { if (!lhs->type()->coerces_to(*rhs->type())) throw Error("expressions are not comparable", loc); } Eq::Eq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): EquatableBinaryExpr(lhs_, rhs_, loc_) { } Eq *Eq::clone() const { return new Eq(*this); } Ptr Eq::type() const { return Boolean; } mpz_class Eq::constant_fold() const { return lhs->constant_fold() == rhs->constant_fold(); } bool Eq::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Eq::to_string() const { return "(" + lhs->to_string() + " = " + rhs->to_string() + ")"; } Neq::Neq(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): EquatableBinaryExpr(lhs_, rhs_, loc_) { } Neq *Neq::clone() const { return new Neq(*this); } Ptr Neq::type() const { return Boolean; } mpz_class Neq::constant_fold() const { return lhs->constant_fold() != rhs->constant_fold(); } bool Neq::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Neq::to_string() const { return "(" + lhs->to_string() + " != " + rhs->to_string() + ")"; } static bool arithmetic(const Expr &lhs, const Expr &rhs) { const Ptr t1 = lhs.type()->resolve(); const Ptr t2 = rhs.type()->resolve(); if (isa(t1) && isa(t2)) return true; return false; } ArithmeticBinaryExpr::ArithmeticBinaryExpr(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): BinaryExpr(lhs_, rhs_, loc_) { } void ArithmeticBinaryExpr::validate() const { if (!arithmetic(*lhs, *rhs)) throw Error("expressions are incompatible in arithmetic expression", loc); } Add::Add(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): ArithmeticBinaryExpr(lhs_, rhs_, loc_) { } Add *Add::clone() const { return new Add(*this); } Ptr Add::type() const { return Ptr::make(nullptr, nullptr, location()); } mpz_class Add::constant_fold() const { return lhs->constant_fold() + rhs->constant_fold(); } bool Add::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Add::to_string() const { return "(" + lhs->to_string() + " + " + rhs->to_string() + ")"; } Sub::Sub(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): ArithmeticBinaryExpr(lhs_, rhs_, loc_) { } Sub *Sub::clone() const { return new Sub(*this); } Ptr Sub::type() const { return Ptr::make(nullptr, nullptr, location()); } mpz_class Sub::constant_fold() const { return lhs->constant_fold() - rhs->constant_fold(); } bool Sub::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Sub::to_string() const { return "(" + lhs->to_string() + " - " + rhs->to_string() + ")"; } Negative::Negative(const Ptr &rhs_, const location &loc_): UnaryExpr(rhs_, loc_) { } void Negative::validate() const { if (!isa(rhs->type()->resolve())) throw Error("expression cannot be negated", rhs->loc); } Negative *Negative::clone() const { return new Negative(*this); } Ptr Negative::type() const { return Ptr::make(nullptr, nullptr, location()); } mpz_class Negative::constant_fold() const { return -rhs->constant_fold(); } bool Negative::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *rhs == *o->rhs; } std::string Negative::to_string() const { return "(-" + rhs->to_string() + ")"; } Mul::Mul(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): ArithmeticBinaryExpr(lhs_, rhs_, loc_) { } Mul *Mul::clone() const { return new Mul(*this); } Ptr Mul::type() const { return Ptr::make(nullptr, nullptr, location()); } mpz_class Mul::constant_fold() const { return lhs->constant_fold() * rhs->constant_fold(); } bool Mul::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Mul::to_string() const { return "(" + lhs->to_string() + " * " + rhs->to_string() + ")"; } Div::Div(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): ArithmeticBinaryExpr(lhs_, rhs_, loc_) { } Div *Div::clone() const { return new Div(*this); } Ptr Div::type() const { return Ptr::make(nullptr, nullptr, location()); } mpz_class Div::constant_fold() const { mpz_class a = lhs->constant_fold(); mpz_class b = rhs->constant_fold(); if (b == 0) throw Error("division by 0 in " + a.get_str() + " / " + b.get_str(), loc); return a / b; } bool Div::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Div::to_string() const { return "(" + lhs->to_string() + " / " + rhs->to_string() + ")"; } Mod::Mod(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): ArithmeticBinaryExpr(lhs_, rhs_, loc_) { } Mod *Mod::clone() const { return new Mod(*this); } Ptr Mod::type() const { return Ptr::make(nullptr, nullptr, location()); } mpz_class Mod::constant_fold() const { mpz_class a = lhs->constant_fold(); mpz_class b = rhs->constant_fold(); if (b == 0) throw Error("mod by 0 in " + a.get_str() + " % " + b.get_str(), loc); return a % b; } bool Mod::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } std::string Mod::to_string() const { return "(" + lhs->to_string() + " % " + rhs->to_string() + ")"; } ExprID::ExprID(const std::string &id_, const Ptr &value_, const location &loc_): Expr(loc_), id(id_), value(value_) { } ExprID *ExprID::clone() const { return new ExprID(*this); } bool ExprID::constant() const { if (isa(value)) return true; if (auto a = dynamic_cast(value.get())) return a->value->constant(); return false; } Ptr ExprID::type() const { if (value == nullptr) throw Error("symbol \"" + id + "\" in expression is unresolved", loc); return value->get_type(); } mpz_class ExprID::constant_fold() const { if (auto c = dynamic_cast(value.get())) return c->value->constant_fold(); if (auto a = dynamic_cast(value.get())) return a->value->constant_fold(); throw Error("symbol \"" + id + "\" is not a constant", loc); } bool ExprID::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (id != o->id) return false; if (value == nullptr) { if (o->value != nullptr) return false; } else if (o->value == nullptr) { return false; } else if (*value != *o->value) { return false; } return true; } void ExprID::validate() const { if (value == nullptr) throw Error("unresolved expression \"" + id + "\"", loc); } bool ExprID::is_lvalue() const { if (value == nullptr) throw Error("unresolved expression \"" + id + "\"", loc); return value->is_lvalue(); } bool ExprID::is_readonly() const { return value->is_readonly(); } std::string ExprID::to_string() const { return id; } bool ExprID::is_literal_true() const { // It is not possible to shadow “true,” so we simply need to check the text of // this expression. Boolean literals are normalised to lower case during // lexing (see lexer.l) so we do not need to worry about case. return id == "true"; } bool ExprID::is_literal_false() const { return id == "false"; } Field::Field(const Ptr &record_, const std::string &field_, const location &loc_): Expr(loc_), record(record_), field(field_) { } Field *Field::clone() const { return new Field(*this); } bool Field::constant() const { return false; } Ptr Field::type() const { const Ptr root = record->type(); const Ptr resolved = root->resolve(); auto r = dynamic_cast(resolved.get()); assert(r != nullptr && "invalid left hand side of field expression"); for (const Ptr &f : r->fields) { if (f->name == field) return f->type; } throw Error("no field named \"" + field + "\" in record", loc); } mpz_class Field::constant_fold() const { throw Error("field expression used in constant", loc); } bool Field::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *record == *o->record && field == o->field; } void Field::validate() const { const Ptr root = record->type()->resolve(); if (!isa(root)) throw Error("left hand side of field expression is not a record", loc); auto r = dynamic_cast(*root); for (const Ptr &f : r.fields) { if (f->name == field) return; } throw Error("no field named \"" + field + "\" in record", loc); } bool Field::is_lvalue() const { return record->is_lvalue(); } bool Field::is_readonly() const { return record->is_readonly(); } std::string Field::to_string() const { return record->to_string() + "." + field; } Element::Element(const Ptr &array_, const Ptr &index_, const location &loc_): Expr(loc_), array(array_), index(index_) { } Element *Element::clone() const { return new Element(*this); } bool Element::constant() const { return false; } Ptr Element::type() const { const Ptr t = array->type()->resolve(); const Array *a = dynamic_cast(t.get()); assert(a != nullptr && "array reference based on something that is not an array"); return a->element_type; } mpz_class Element::constant_fold() const { throw Error("array element used in constant", loc); } bool Element::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *array == *o->array && *index == *o->index; } void Element::validate() const { const Ptr t = array->type()->resolve();; if (!isa(t)) throw Error("array index on an expression that is not an array", loc); auto a = dynamic_cast(*t); if (!index->type()->coerces_to(*a.index_type)) throw Error("array indexed using an expression of incorrect type", loc); } bool Element::is_lvalue() const { return array->is_lvalue(); } bool Element::is_readonly() const { return array->is_readonly(); } std::string Element::to_string() const { return array->to_string() + "[" + index->to_string() + "]"; } FunctionCall::FunctionCall(const std::string &name_, const std::vector> &arguments_, const location &loc_): Expr(loc_), name(name_), arguments(arguments_) { } FunctionCall *FunctionCall::clone() const { return new FunctionCall(*this); } bool FunctionCall::constant() const { /* TODO: For now, we conservatively treat function calls as non-constant. In * future, it would be nice to lift this restriction to support more advanced * parameterised models. */ return false; } Ptr FunctionCall::type() const { if (function == nullptr) throw Error("unresolved function call \"" + name + "\"", loc); if (function->return_type == nullptr) throw Error("procedure calls have no type", loc); return function->return_type; } mpz_class FunctionCall::constant_fold() const { // See FunctionCall::constant() regarding conservatism here. throw Error("function call used in a constant", loc); } bool FunctionCall::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (name != o->name) return false; if (function == nullptr) { if (o->function != nullptr) return false; } else if (o->function == nullptr) { return false; } else if (*function != *o->function) { return false; } if (!vector_eq(arguments, o->arguments)) return false; if (within_procedure_call != o->within_procedure_call) return false; return true; } void FunctionCall::validate() const { if (function == nullptr) throw Error("unknown function call \"" + name + "\"", loc); if (arguments.size() != function->parameters.size()) throw Error("incorrect number of parameters passed to function", loc); if (!within_procedure_call && function->return_type == nullptr) throw Error("procedure (function with no return value) called in " "expression", loc); auto it = arguments.begin(); for (const Ptr &v : function->parameters) { assert(it != arguments.end() && "mismatch in size of parameter list and " "function arguments list"); if ((*it)->is_readonly() && !v->is_readonly()) throw Error("function call passes a read-only value as a var parameter", (*it)->loc); if (!(*it)->type()->coerces_to(*v->get_type())) throw Error("function call contains parameter of incorrect type", (*it)->loc); const Ptr param_type = v->get_type()->resolve(); // if this is a writable range-typed parameter, we additionally require it // to be of exactly the same type in order to guarantee the caller’s and // callee’s handles are compatible if (!v->is_readonly() && isa(param_type)) { const Ptr arg_type = (*it)->type()->resolve(); assert(isa(arg_type) && "non-range considered type-compatible with range"); auto p = dynamic_cast(*param_type); auto a = dynamic_cast(*arg_type); if (p.min->constant_fold() != a.min->constant_fold() || p.max->constant_fold() != a.max->constant_fold()) throw Error("range types of function call argument and var parameter " "differ", (*it)->loc); } it++; } } std::string FunctionCall::to_string() const { std::string s = name + "("; bool first = true; for (const Ptr &arg : arguments) { if (!first) s += ", "; s += arg->to_string(); } s += ")"; return s; } Quantifier::Quantifier(const std::string &name_, const Ptr &type_, const location &loc_): Node(loc_), name(name_), type(type_), decl(Ptr::make(name_, type_, loc_)) { } Quantifier::Quantifier(const std::string &name_, const Ptr &from_, const Ptr &to_, const location &loc_): Quantifier(name_, from_, to_, nullptr, loc_) { } Quantifier::Quantifier(const std::string &name_, const Ptr &from_, const Ptr &to_, const Ptr &step_, const location &loc_): Node(loc_), name(name_), from(from_), to(to_), step(step_), // we construct an artificial unbounded range here because we do not know // whether the bounds of this iteration are constant prior to symbol // resolution decl(Ptr::make(name_, Ptr::make(nullptr, nullptr, loc_), loc_)) { } Quantifier *Quantifier::clone() const { return new Quantifier(*this); } bool Quantifier::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (name != o->name) return false; if (type == nullptr) { if (o->type != nullptr) return false; } else if (o->type == nullptr) { return false; } else if (*type != *o->type) { return false; } if (from == nullptr) { if (o->from != nullptr) return false; } else if (o->from == nullptr) { return false; } else if (*from != *o->from) { return false; } if (to == nullptr) { if (o->to != nullptr) return false; } else if (o->to == nullptr) { return false; } else if (*to != *o->to) { return false; } if (step == nullptr) { if (o->step != nullptr) return false; } else if (o->step == nullptr) { return false; } else if (*step != *o->step) { return false; } return true; } void Quantifier::validate() const { bool from_const = from != nullptr && from->constant(); bool to_const = to != nullptr && to->constant(); bool step_const = step != nullptr && step->constant(); if (step_const && step->constant_fold() == 0) throw Error("infinite loop due to 0 step", loc); bool step_positive = step == nullptr || (step_const && step->constant_fold() > 0); bool step_negative = step != nullptr && step_const && step->constant_fold() < 0; if (from_const && to_const) { bool up_count = from->constant_fold() < to->constant_fold(); bool down_count = from->constant_fold() > to->constant_fold(); if (up_count && step_negative) throw Error("infinite loop due to inverted step", loc); if (down_count && step_positive) throw Error("infinite loop due to inverted step", loc); } } std::string Quantifier::to_string() const { if (type == nullptr) { std::string s = name + " from " + from->to_string() + " to " + to->to_string(); if (step != nullptr) s += " by " + step->to_string(); return s; } return name + " : " + type->to_string(); } bool Quantifier::constant() const { if (type != nullptr) { assert(type->is_simple() && "complex type used in quantifier"); if (!type->constant()) return false; } if (from != nullptr && !from->constant()) return false; if (to != nullptr && !to->constant()) return false; if (step != nullptr && !step->constant()) return false; return true; } mpz_class Quantifier::count() const { if (!constant()) throw Error("non-constant quantifier is uncountable", loc); if (type != nullptr) { // subtract 1 because quantified variable can never be 'undefined' return type->count() - 1; } assert(from != nullptr && to != nullptr && "quantifier with null type and " "bounds"); mpz_class lb = from->constant_fold(); mpz_class ub = to->constant_fold(); mpz_class inc = step == nullptr ? 1 : step->constant_fold(); mpz_class c = 0; for (mpz_class i = lb; i <= ub; i += inc) c++; return c; } std::string Quantifier::lower_bound() const { if (!constant()) throw Error("non-constant quantifier has a lower bound that cannot be " "calculated ahead of time", loc); if (type != nullptr) return type->lower_bound(); assert(from != nullptr && "quantifier with null type and null lower bound"); return "VALUE_C(" + from->constant_fold().get_str() + ")"; } Exists::Exists(const Quantifier &quantifier_, const Ptr &expr_, const location &loc_): Expr(loc_), quantifier(quantifier_), expr(expr_) { } Exists *Exists::clone() const { return new Exists(*this); } bool Exists::constant() const { return expr->constant(); } Ptr Exists::type() const { return Boolean; } bool Exists::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && quantifier == o->quantifier && *expr == *o->expr; } mpz_class Exists::constant_fold() const { throw Error("exists expression used in constant", loc); } void Exists::validate() const { if (!expr->is_boolean()) throw Error("expression in exists is not boolean", expr->loc); } std::string Exists::to_string() const { return "exists " + quantifier.to_string() + " do " + expr->to_string() + " endexists"; } Forall::Forall(const Quantifier &quantifier_, const Ptr &expr_, const location &loc_): Expr(loc_), quantifier(quantifier_), expr(expr_) { } Forall *Forall::clone() const { return new Forall(*this); } bool Forall::constant() const { return expr->constant(); } Ptr Forall::type() const { return Boolean; } mpz_class Forall::constant_fold() const { throw Error("forall expression used in constant", loc); } bool Forall::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && quantifier == o->quantifier && *expr == *o->expr; } void Forall::validate() const { if (!expr->is_boolean()) throw Error("expression in forall is not boolean", expr->loc); } std::string Forall::to_string() const { return "forall " + quantifier.to_string() + " do " + expr->to_string() + " endforall"; } IsUndefined::IsUndefined(const Ptr &expr_, const location &loc_): Expr(loc_), expr(expr_) { } IsUndefined *IsUndefined::clone() const { return new IsUndefined(*this); } bool IsUndefined::constant() const { return false; } Ptr IsUndefined::type() const { return Boolean; } mpz_class IsUndefined::constant_fold() const { throw Error("isundefined used in constant", loc); } bool IsUndefined::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (*expr != *o->expr) return false; return true; } void IsUndefined::validate() const { if (!expr->is_lvalue()) throw Error("non-lvalue expression cannot be used in isundefined", expr->loc); const Ptr t = expr->type(); if (!t->is_simple()) throw Error("complex type used in isundefined", expr->loc); } std::string IsUndefined::to_string() const { return "isundefined(" + expr->to_string() + ")"; } } rumur-2020.02.17/librumur/src/Function.cc000066400000000000000000000121331362265074000200100ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include namespace rumur { Function::Function(const std::string &name_, const std::vector> ¶meters_, const Ptr &return_type_, const std::vector> &decls_, const std::vector> &body_, const location &loc_): Node(loc_), name(name_), parameters(parameters_), return_type(return_type_), decls(decls_), body(body_) { } Function *Function::clone() const { return new Function(*this); } bool Function::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (name != o->name) return false; if (!vector_eq(parameters, o->parameters)) return false; if (return_type == nullptr) { if (o->return_type != nullptr) return false; } else { if (o->return_type == nullptr) return false; if (*return_type != *o->return_type) return false; } if (!vector_eq(decls, o->decls)) return false; if (!vector_eq(body, o->body)) return false; return true; } void Function::validate() const { /*Define a traversal that checks our contained return statements for * correctness. */ class ReturnChecker : public ConstTraversal { private: const TypeExpr *return_type; public: ReturnChecker(const TypeExpr *rt): return_type(rt) { } void visit_return(const Return &n) final { if (return_type == nullptr) { if (n.expr != nullptr) throw Error("statement returns a value from a procedure", n.loc); } else { if (n.expr == nullptr) throw Error("empty return statement in a function", n.loc); if (!n.expr->type()->coerces_to(*return_type)) throw Error("returning incompatible typed value from a function", n.loc); } } virtual ~ReturnChecker() = default; }; // Run the checker ReturnChecker rt(return_type.get()); for (auto &s : body) rt.dispatch(*s); } bool Function::is_pure() const { // if the function takes a var parameter, consider it impure for (const Ptr &p : parameters) { if (!p->is_readonly()) return false; } // a traversal that looks for impure features class PurityDetector : public ConstTraversal { private: const Function *root; // is this a reference to a state variable? bool is_global_ref(const ExprDecl &expr) const { if (auto e = dynamic_cast(&expr)) return is_global_ref(*e->value); if (auto e = dynamic_cast(&expr)) return e->is_in_state(); return false; } bool is_global_ref(const Expr &expr) const { if (auto e = dynamic_cast(&expr)) return is_global_ref(*e->value); if (auto e = dynamic_cast(&expr)) return is_global_ref(*e->record); if (auto e = dynamic_cast(&expr)) return is_global_ref(*e->array); return false; } public: bool pure = true; PurityDetector(const Function &root_): root(&root_) { } void visit_assignment(const Assignment &n) final { pure &= !is_global_ref(*n.lhs); dispatch(*n.lhs); dispatch(*n.rhs); } void visit_clear(const Clear &n) final { pure &= !is_global_ref(*n.rhs); dispatch(*n.rhs); } void visit_errorstmt(const ErrorStmt&) final { // treat any error as a side effect pure = false; } void visit_functioncall(const FunctionCall &n) final { assert(n.function != nullptr && "unresolved function call"); for (const Ptr &a : n.arguments) dispatch(*a); // check if this is a recursive call, to avoid looping if (n.function->unique_id != root->unique_id) pure &= n.function->is_pure(); } void visit_propertystmt(const PropertyStmt&) final { // treat any property statement as a side effect pure = false; // no need to further descend } void visit_put(const Put&) final { pure = false; // no need to descend into children } void visit_undefine(const Undefine &n) final { pure &= !is_global_ref(*n.rhs); dispatch(*n.rhs); } }; // run the traversal on ourselves PurityDetector pd(*this); pd.dispatch(*this); return pd.pure; } bool Function::is_recursive() const { // a traversal that finds calls to a given function class CallFinder : public ConstTraversal { private: const Function *needle; public: bool found = false; explicit CallFinder(const Function &needle_): needle(&needle_) { } void visit_functioncall(const FunctionCall &n) final { if (n.function != nullptr && n.function->unique_id == needle->unique_id) found = true; } virtual ~CallFinder() = default; }; // run the traversal on ourselves CallFinder cf(*this); cf.dispatch(*this); return cf.found; } } rumur-2020.02.17/librumur/src/Model.cc000066400000000000000000000063401362265074000172660ustar00rootroot00000000000000#include #include #include #include #include #include "location.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include namespace rumur { Model::Model(const std::vector> &decls_, const std::vector> &functions_, const std::vector> &rules_, const location &loc_): Node(loc_), decls(decls_), functions(functions_), rules(rules_) { } Model *Model::clone() const { return new Model(*this); } mpz_class Model::size_bits() const { mpz_class s = 0; for (const Ptr &d : decls) { if (auto v = dynamic_cast(d.get())) s += v->type->width(); } return s; } bool Model::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (!vector_eq(decls, o->decls)) return false; if (!vector_eq(functions, o->functions)) return false; if (!vector_eq(rules, o->rules)) return false; return true; } void Model::validate() const { // Check all state variable names are distinct. { std::unordered_set names; for (const Ptr &d : decls) { if (auto v = dynamic_cast(d.get())) { if (!names.insert(v->name).second) throw Error("duplicate state variable name \"" + v->name + "\"", v->loc); } } } } mpz_class Model::liveness_count() const { // Define a traversal for counting liveness properties. class LivenessCounter : public ConstTraversal { public: mpz_class count = 0; mpz_class multiplier = 1; void visit_ruleset(const Ruleset &n) final { /* Adjust the multiplier for the number of copies of the contained rules * we will eventually generate. */ for (const Quantifier &q : n.quantifiers) { assert (q.constant() && "non-constant quantifier in ruleset"); multiplier *= q.count(); } // Descend into our children; we can ignore the quantifiers themselves. for (const Ptr &r : n.rules) dispatch(*r); /* Reduce the multiplier, removing our effect. This juggling is necessary * because we ourselves may be within a ruleset or contain further * rulesets. */ for (const Quantifier &q : n.quantifiers) { assert(multiplier % q.count() == 0 && "logic error in handling " "LivenessCounter::multiplier"); multiplier /= q.count(); } } void visit_propertyrule(const PropertyRule &n) final { if (n.property.category == Property::LIVENESS) count += multiplier; // No need to descend into child nodes. } virtual ~LivenessCounter() = default; }; // Use the traversal to count liveness rules. LivenessCounter lc; lc.dispatch(*this); return lc.count; } void Model::reindex() { // Re-number our and our children's 'unique_id' members Indexer i; i.dispatch(*this); } } rumur-2020.02.17/librumur/src/Node.cc000066400000000000000000000001641362265074000171110ustar00rootroot00000000000000#include #include namespace rumur { Node::Node(const location &loc_): loc(loc_) { } } rumur-2020.02.17/librumur/src/Number.cc000066400000000000000000000020671362265074000174600ustar00rootroot00000000000000#include #include #include #include #include #include "location.hh" #include #include #include #include #include #include #include namespace rumur { Number::Number(const std::string &value_, const location &loc_) try: Expr(loc_), value(value_) { } catch (std::invalid_argument &e) { throw Error("invalid number: " + value_, loc_); } Number::Number(const mpz_class &value_, const location &loc_): Expr(loc_), value(value_) { } Number *Number::clone() const { return new Number(*this); } bool Number::constant() const { return true; } Ptr Number::type() const { return Ptr::make(nullptr, nullptr, location()); } mpz_class Number::constant_fold() const { return value; } bool Number::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && value == o->value; } std::string Number::to_string() const { return value.get_str(); } } rumur-2020.02.17/librumur/src/Property.cc000066400000000000000000000007361362265074000200550ustar00rootroot00000000000000#include #include #include namespace rumur { Property::Property(Category category_, const Ptr &expr_, const location &loc_): Node(loc_), category(category_), expr(expr_) { } Property *Property::clone() const { return new Property(*this); } bool Property::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && category == o->category && *expr == *o->expr; } } rumur-2020.02.17/librumur/src/Rule.cc000066400000000000000000000132231362265074000171330ustar00rootroot00000000000000#include #include #include #include "location.hh" #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include namespace rumur { namespace { /* A traversal pass that checks any return statements within a rule do not * have a trailing expression. */ class ReturnChecker : public ConstTraversal { public: /* Avoid recursing into functions, that may have return statements with an * expression. */ void visit_function(const Function&) final { } void visit_functioncall(const FunctionCall&) final { } void visit_procedurecall(const ProcedureCall&) final { } void visit_return(const Return &n) final { if (n.expr != nullptr) throw Error("return statement in rule or startstate returns a value", n.loc); // No need to recurse into the return statement's child. } static void check(const Node &n) { ReturnChecker c; c.dispatch(n); } virtual ~ReturnChecker() = default; }; } Rule::Rule(const std::string &name_, const location &loc_): Node(loc_), name(name_) { } std::vector> Rule::flatten() const { return { Ptr(clone()) }; } AliasRule::AliasRule(const std::vector> &aliases_, const std::vector> &rules_, const location &loc_): Rule("", loc_), rules(rules_) { aliases = aliases_; } AliasRule *AliasRule::clone() const { return new AliasRule(*this); } bool AliasRule::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (name != o->name) return false; if (quantifiers != o->quantifiers) return false; if (!vector_eq(aliases, o->aliases)) return false; if (!vector_eq(rules, o->rules)) return false; return true; } std::vector> AliasRule::flatten() const { std::vector> rs; for (const Ptr &r : rules) { for (Ptr &f : r->flatten()) { for (const Ptr &a : aliases) f->aliases.insert(f->aliases.begin(), a); rs.push_back(f); } } return rs; } SimpleRule::SimpleRule(const std::string &name_, const Ptr &guard_, const std::vector> &decls_, const std::vector> &body_, const location &loc_): Rule(name_, loc_), guard(guard_), decls(decls_), body(body_) { } SimpleRule *SimpleRule::clone() const { return new SimpleRule(*this); } bool SimpleRule::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (name != o->name) return false; if (quantifiers != o->quantifiers) return false; if (!vector_eq(aliases, o->aliases)) return false; if (guard == nullptr) { if (o->guard != nullptr) return false; } else { if (o->guard == nullptr || *guard != *o->guard) return false; } if (!vector_eq(decls, o->decls)) return false; if (!vector_eq(body, o->body)) return false; return true; } void SimpleRule::validate() const { ReturnChecker::check(*this); } StartState::StartState(const std::string &name_, const std::vector> &decls_, const std::vector> &body_, const location &loc_): Rule(name_, loc_), decls(decls_), body(body_) { } StartState *StartState::clone() const { return new StartState(*this); } bool StartState::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (name != o->name) return false; if (quantifiers != o->quantifiers) return false; if (!vector_eq(aliases, o->aliases)) return false; if (!vector_eq(decls, o->decls)) return false; if (!vector_eq(body, o->body)) return false; return true; } void StartState::validate() const { ReturnChecker::check(*this); } PropertyRule::PropertyRule(const std::string &name_, const Property &property_, const location &loc_): Rule(name_, loc_), property(property_) { } PropertyRule *PropertyRule::clone() const { return new PropertyRule(*this); } bool PropertyRule::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (name != o->name) return false; if (quantifiers != o->quantifiers) return false; if (!vector_eq(aliases, o->aliases)) return false; if (property != o->property) return false; return true; } Ruleset::Ruleset(const std::vector &quantifiers_, const std::vector> &rules_, const location &loc_): Rule("", loc_), rules(rules_) { quantifiers = quantifiers_; } Ruleset *Ruleset::clone() const { return new Ruleset(*this); } bool Ruleset::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (name != o->name) return false; if (quantifiers != o->quantifiers) return false; if (!vector_eq(aliases, o->aliases)) return false; if (!vector_eq(rules, o->rules)) return false; return true; } void Ruleset::validate() const { for (const Quantifier &q : quantifiers) { if (!q.constant()) throw Error("non-constant quantifier expression as ruleset parameter", q.loc); } } std::vector> Ruleset::flatten() const { std::vector> rs; for (const Ptr &r : rules) { for (Ptr &f : r->flatten()) { for (const Quantifier &q : quantifiers) f->quantifiers.push_back(q); rs.push_back(f); } } return rs; } } rumur-2020.02.17/librumur/src/Stmt.cc000066400000000000000000000221331362265074000171530ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include namespace rumur { Stmt::Stmt(const location &loc_): Node(loc_) { } AliasStmt::AliasStmt(const std::vector> &aliases_, const std::vector> &body_, const location &loc_): Stmt(loc_), aliases(aliases_), body(body_) { } AliasStmt *AliasStmt::clone() const { return new AliasStmt(*this); } bool AliasStmt::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (!vector_eq(aliases, o->aliases)) return false; if (!vector_eq(body, o->body)) return false; return true; } PropertyStmt::PropertyStmt(const Property &property_, const std::string &message_, const location &loc_): Stmt(loc_), property(property_), message(message_) { } PropertyStmt *PropertyStmt::clone() const { return new PropertyStmt(*this); } bool PropertyStmt::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && property == o->property && message == o->message; } void PropertyStmt::validate() const { if (property.category == Property::LIVENESS) throw Error("liveness property appearing as a statement instead of a top " "level property", loc); } Assignment::Assignment(const Ptr &lhs_, const Ptr &rhs_, const location &loc_): Stmt(loc_), lhs(lhs_), rhs(rhs_) { } Assignment *Assignment::clone() const { return new Assignment(*this); } bool Assignment::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && *lhs == *o->lhs && *rhs == *o->rhs; } void Assignment::validate() const { if (!lhs->is_lvalue()) throw Error("non-lvalue expression cannot be assigned to", loc); if (lhs->is_readonly()) throw Error("read-only expression cannot be assigned to", loc); if (!lhs->type()->coerces_to(*rhs->type())) throw Error("invalid assignment from incompatible type", loc); } Clear::Clear(const Ptr &rhs_, const location &loc_): Stmt(loc_), rhs(rhs_) { } Clear *Clear::clone() const { return new Clear(*this); } bool Clear::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (*rhs != *o->rhs) return false; return true; } void Clear::validate() const { if (!rhs->is_lvalue()) throw Error("invalid clear of non-lvalue expression", loc); if (rhs->is_readonly()) throw Error("invalid clear of read-only expression", loc); } ErrorStmt::ErrorStmt(const std::string &message_, const location &loc_): Stmt(loc_), message(message_) { } ErrorStmt *ErrorStmt::clone() const { return new ErrorStmt(*this); } bool ErrorStmt::operator==(const Node &other) const { auto o = dynamic_cast(&other); return o != nullptr && message == o->message; } For::For(const Quantifier &quantifier_, const std::vector> &body_, const location &loc_): Stmt(loc_), quantifier(quantifier_), body(body_) { } For *For::clone() const { return new For(*this); } bool For::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (quantifier != o->quantifier) return false; if (!vector_eq(body, o->body)) return false; return true; } IfClause::IfClause(const Ptr &condition_, const std::vector> &body_, const location &loc_): Node(loc_), condition(condition_), body(body_) { } IfClause *IfClause::clone() const { return new IfClause(*this); } bool IfClause::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (condition == nullptr) { if (o->condition != nullptr) return false; } else if (o->condition == nullptr) { return false; } else if (*condition != *o->condition) { return false; } if (!vector_eq(body, o->body)) return false; return true; } void IfClause::validate() const { if (condition == nullptr) { // This is an 'else' block. return; } // Only boolean conditions are supported. if (!condition->is_boolean()) throw Error("condition of if clause is not a boolean expression", loc); } If::If(const std::vector &clauses_, const location &loc_): Stmt(loc_), clauses(clauses_) { } If *If::clone() const { return new If(*this); } bool If::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (clauses != o->clauses) return false; return true; } ProcedureCall::ProcedureCall(const std::string &name, const std::vector> &arguments, const location &loc_): Stmt(loc_), call(name, arguments, loc_) { call.within_procedure_call = true; } ProcedureCall *ProcedureCall::clone() const { return new ProcedureCall(*this); } bool ProcedureCall::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (call != o->call) return false; return true; } Put::Put(const std::string &value_, const location &loc_): Stmt(loc_), value(value_) { } Put::Put(const Ptr &expr_, const location &loc_): Stmt(loc_), expr(expr_) { } Put *Put::clone() const { return new Put(*this); } bool Put::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (value != o->value) return false; if (expr == nullptr) { if (o->expr != nullptr) return false; } else if (o->expr == nullptr) { return false; } else if (*expr != *o->expr) { return false; } return true; } void Put::validate() const { if (expr != nullptr && !expr->is_lvalue() && !expr->type()->is_simple()) throw Error("printing a complex non-lvalue is not supported", loc); } Return::Return(const Ptr &expr_, const location &loc_): Stmt(loc_), expr(expr_) { } Return *Return::clone() const { return new Return(*this); } bool Return::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (expr == nullptr) { if (o->expr != nullptr) return false; } else if (o->expr == nullptr) { return false; } else if (*expr != *o->expr) { return false; } return true; } SwitchCase::SwitchCase(const std::vector> &matches_, const std::vector> &body_, const location &loc_): Node(loc_), matches(matches_), body(body_) { } SwitchCase *SwitchCase::clone() const { return new SwitchCase(*this); } bool SwitchCase::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (!vector_eq(matches, o->matches)) return false; if (!vector_eq(body, o->body)) return false; return true; } Switch::Switch(const Ptr &expr_, const std::vector &cases_, const location &loc_): Stmt(loc_), expr(expr_), cases(cases_) { } Switch *Switch::clone() const { return new Switch(*this); } bool Switch::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (*expr != *o->expr) return false; if (cases != o->cases) return false; return true; } void Switch::validate() const { const Ptr t = expr->type(); if (!t->is_simple()) throw Error("switch expression has complex type", expr->loc); for (const SwitchCase &c : cases) { for (const Ptr &e : c.matches) { if (!t->coerces_to(*e->type())) throw Error("expression in case cannot be compared to switch " "expression", e->loc); } } } Undefine::Undefine(const Ptr &rhs_, const location &loc_): Stmt(loc_), rhs(rhs_) { } Undefine *Undefine::clone() const { return new Undefine(*this); } bool Undefine::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (*rhs != *o->rhs) return false; return true; } void Undefine::validate() const { if (!rhs->is_lvalue()) throw Error("invalid undefine of non-lvalue expression", loc); if (rhs->is_readonly()) throw Error("invalid undefine of read-only expression", loc); } While::While(const Ptr &condition_, const std::vector> &body_, const location &loc_): Stmt(loc_), condition(condition_), body(body_) { } While *While::clone() const { return new While(*this); } bool While::operator==(const Node &other) const { auto o = dynamic_cast(&other); if (o == nullptr) return false; if (*condition != *o->condition) return false; if (!vector_eq(body, o->body)) return false; return true; } void While::validate() const { if (!condition->is_boolean()) throw Error("condition in while loop is not a boolean expression", condition->loc); } } rumur-2020.02.17/librumur/src/TypeExpr.cc000066400000000000000000000331641362265074000200120ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include namespace rumur { TypeExpr::TypeExpr(const location &loc_): Node(loc_) { } bool TypeExpr::is_simple() const { return false; } Ptr TypeExpr::resolve() const { return Ptr(clone()); } std::string TypeExpr::lower_bound() const { throw Error("complex types do not have valid lower bounds", loc); } std::string TypeExpr::upper_bound() const { throw Error("complex types do not have valid upper bounds", loc); } mpz_class TypeExpr::width() const { mpz_class c = count(); // If there are 0 or 1 values of this type, its width is trivial. if (c <= 1) return 0; /* Otherwise, we need the number of bits required to represent the largest * value. */ mpz_class largest(c - 1); mpz_class bits(0); while (largest != 0) { bits++; largest >>= 1; } return bits; } bool TypeExpr::constant() const { assert(!is_simple() && "TypeExpr::constant invoked for simple type; missing " "TypeExpr::constant override?"); throw Error("complex types do not have bounds to query", loc); } // Compare two types for equality. This is implemented here rather than // operator== because its semantics are not exactly what you would expect from // operator== and we do not want to expose it to other files. static bool equal(const TypeExpr &t1, const TypeExpr &t2) { class Equater : public ConstTypeTraversal { private: const Ptr t; public: bool result = true; explicit Equater(const TypeExpr &type): t(type.resolve()) { } void visit_array(const Array &n) final { if (auto a = dynamic_cast(t.get())) { result &= equal(*a->index_type, *n.index_type); result &= equal(*a->element_type, *n.element_type); } else { result = false; } } void visit_enum(const Enum &n) final { if (auto e = dynamic_cast(t.get())) { for (auto it = e->members.begin(), it2 = n.members.begin(); ; it++, it2++) { if (it == e->members.end()) { result &= it2 == n.members.end(); break; } if (it2 == n.members.end()) { result = false; break; } result &= it->first == it2->first; } } else { result = false; } } void visit_range(const Range &n) final { if (auto r = dynamic_cast(t.get())) { result = r->min->constant_fold() == n.min->constant_fold() && r->max->constant_fold() == n.max->constant_fold(); } else { result = false; } } void visit_record(const Record &n) final { if (auto r = dynamic_cast(t.get())) { for (auto it = r->fields.begin(), it2 = n.fields.begin(); ; it++, it2++) { if (it == r->fields.end()) { result &= it2 == n.fields.end(); break; } if (it2 == n.fields.end()) { result = false; break; } result &= (*it)->name == (*it2)->name && equal(*(*it)->get_type(), *(*it2)->get_type()); } } else { result = false; } } void visit_scalarset(const Scalarset &n) final { if (auto s = dynamic_cast(t.get())) { result = s->bound->constant_fold() == n.bound->constant_fold(); } else { result = false; } } void visit_typeexprid(const TypeExprID &n) final { dispatch(*n.referent); } }; Equater eq(t1); eq.dispatch(t2); return eq.result; } bool TypeExpr::coerces_to(const TypeExpr &other) const { const Ptr t1 = resolve(); const Ptr t2 = other.resolve(); if (isa(t1) && isa(t2)) return true; return equal(*t1, *t2); } bool TypeExpr::is_boolean() const { return false; } bool TypeExpr::equatable_with(const TypeExpr &other) const { return coerces_to(other); } Range::Range(const Ptr &min_, const Ptr &max_, const location &loc_): TypeExpr(loc_), min(min_), max(max_) { if (min == nullptr) { // FIXME: avoid hard coding INT64 limits here // FIXME: this isn't even the right limit because of - overflowing grr... min = Ptr::make(mpz_class("-9223372036854775807"), location()); } if (max == nullptr) { max = Ptr::make(mpz_class("9223372036854775807"), location()); } } Range *Range::clone() const { return new Range(*this); } mpz_class Range::count() const { mpz_class lb = min->constant_fold(); mpz_class ub = max->constant_fold(); return ub - lb + 2; } bool Range::operator==(const Node &other) const { if (auto o = dynamic_cast(&other)) return min->constant_fold() == o->min->constant_fold() && max->constant_fold() == o->max->constant_fold(); if (auto o = dynamic_cast(&other)) return *o == *this; return false; } bool Range::is_simple() const { return true; } void Range::validate() const { if (!min->constant()) throw Error("lower bound of range is not a constant", min->loc); if (!max->constant()) throw Error("upper bound of range is not a constant", max->loc); if (max->constant_fold() < min->constant_fold()) throw Error("upper bound of range is less than lower bound", loc); } std::string Range::lower_bound() const { return "VALUE_C(" + min->constant_fold().get_str() + ")"; } std::string Range::upper_bound() const { return "VALUE_C(" + max->constant_fold().get_str() + ")"; } std::string Range::to_string() const { return min->to_string() + ".." + max->to_string(); } bool Range::constant() const { return min->constant() && max->constant(); } Scalarset::Scalarset(const Ptr &bound_, const location &loc_): TypeExpr(loc_), bound(bound_) { } Scalarset *Scalarset::clone() const { return new Scalarset(*this); } mpz_class Scalarset::count() const { mpz_class b = bound->constant_fold(); assert(b > 0 && "non-positive bound for scalarset"); return b + 1; } bool Scalarset::operator==(const Node &other) const { if (auto o = dynamic_cast(&other)) return bound->constant_fold() == o->bound->constant_fold(); if (auto o = dynamic_cast(&other)) return *o == *this; return false; } bool Scalarset::is_simple() const { return true; } void Scalarset::validate() const { if (!bound->constant()) throw Error("bound of scalarset is not a constant", bound->loc); if (bound->constant_fold() <= 0) throw Error("bound of scalarset is not positive", bound->loc); } std::string Scalarset::lower_bound() const { return "VALUE_C(0)"; } std::string Scalarset::upper_bound() const { mpz_class b = bound->constant_fold() - 1; return "VALUE_C(" + b.get_str() + ")"; } std::string Scalarset::to_string() const { return "scalarset(" + bound->to_string() + ")"; } bool Scalarset::constant() const { return bound->constant(); } Enum::Enum(const std::vector> &members_, const location &loc_): TypeExpr(loc_), members(members_) { } Enum *Enum::clone() const { return new Enum(*this); } mpz_class Enum::count() const { mpz_class members_size = members.size(); return members_size + 1; } bool Enum::operator==(const Node &other) const { if (auto o = dynamic_cast(&other)) { for (auto it = members.begin(), it2 = o->members.begin(); ; it++, it2++) { if (it == members.end()) { if (it2 != o->members.end()) return false; break; } if (it2 == o->members.end()) return false; if ((*it).first != (*it2).first) return false; } return true; } if (auto o = dynamic_cast(&other)) return *o == *this; return false; } bool Enum::is_simple() const { return true; } void Enum::validate() const { std::unordered_set ms; for (const std::pair &member : members) { auto it = ms.insert(member.first); if (!it.second) throw Error("duplicate enum member \"" + member.first + "\"", member.second); } } std::string Enum::lower_bound() const { return "VALUE_C(0)"; } std::string Enum::upper_bound() const { mpz_class size = members.size(); if (size > 0) size--; return "VALUE_C(" + size.get_str() + ")"; } std::string Enum::to_string() const { std::string s = "enum { "; bool first = true; for (const std::pair &m : members) { if (!first) s += ", "; s += m.first; first = false; } return s + " }"; } bool Enum::constant() const { // enums always have a known constant bound return true; } bool Enum::is_boolean() const { // the boolean literals cannot be shadowed, so we simply need to check if our // members are “false” and “true” return members.size() == 2 && members[0].first == "false" && members[1].first == "true"; } Record::Record(const std::vector> &fields_, const location &loc_): TypeExpr(loc_), fields(fields_) { std::unordered_set names; for (const Ptr &f : fields) { if (!names.insert(f->name).second) throw Error("duplicate field name \"" + f->name + "\"", f->loc); } } Record *Record::clone() const { return new Record(*this); } mpz_class Record::width() const { mpz_class s = 0; for (const Ptr &v : fields) s += v->type->width(); return s; } mpz_class Record::count() const { mpz_class s = 1; for (const Ptr &v : fields) s *= v->type->count(); return s; } bool Record::operator==(const Node &other) const { if (auto o = dynamic_cast(&other)) { if (!vector_eq(fields, o->fields)) return false; return true; } if (auto o = dynamic_cast(&other)) return *o == *this; return false; } std::string Record::to_string() const { std::string s = "record "; for (const Ptr &v : fields) s += v->name + " : " + v->type->to_string() + "; "; return s + "endrecord"; } Array::Array(const Ptr &index_type_, const Ptr &element_type_, const location &loc_): TypeExpr(loc_), index_type(index_type_), element_type(element_type_) { } Array *Array::clone() const { return new Array(*this); } mpz_class Array::width() const { mpz_class i = index_type->count(); mpz_class e = element_type->width(); assert(i >= 1 && "index count apparently does not include undefined"); i--; return i * e; } mpz_class Array::count() const { mpz_class i = index_type->count(); mpz_class e = element_type->count(); assert(i >= 1 && "index count apparently does not include undefined"); i--; if (i == 0) return 0; mpz_class s = 1; for (size_t j = 0; j < i; j++) s *= e; return s; } bool Array::operator==(const Node &other) const { if (auto o = dynamic_cast(&other)) return *index_type == *o->index_type && *element_type == *o->element_type; if (auto o = dynamic_cast(&other)) return *o == *this; return false; } void Array::validate() const { if (!index_type->is_simple()) throw Error("array indices must be simple types", loc); } std::string Array::to_string() const { return "array [" + index_type->to_string() + "] of " + element_type->to_string(); } TypeExprID::TypeExprID(const std::string &name_, const Ptr &referent_, const location &loc_): TypeExpr(loc_), name(name_), referent(referent_) { } TypeExprID *TypeExprID::clone() const { return new TypeExprID(*this); } mpz_class TypeExprID::width() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->width(); } mpz_class TypeExprID::count() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->count(); } bool TypeExprID::operator==(const Node &other) const { if (referent != nullptr) return *referent->value == other; auto o = dynamic_cast(&other); if (o == nullptr) return false; if (o->referent != nullptr) return false; // Compare by name if we got to this point. Note that the name is irrelevant // if either type was resolved, as we want to consider resolved types to be // equal if they have equal referents. if (name != o->name) return false; return true; } bool TypeExprID::is_simple() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->is_simple(); } Ptr TypeExprID::resolve() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->resolve(); } void TypeExprID::validate() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); } std::string TypeExprID::lower_bound() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->lower_bound(); } std::string TypeExprID::upper_bound() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->upper_bound(); } std::string TypeExprID::to_string() const { return name; } bool TypeExprID::constant() const { if (referent == nullptr) throw Error("unresolved type symbol \"" + name + "\"", loc); return referent->value->constant(); } } rumur-2020.02.17/librumur/src/except.cc000066400000000000000000000005471362265074000175210ustar00rootroot00000000000000#include #include "location.hh" #include #include #include namespace rumur { Error::Error(const std::string &message, const location &loc_): std::runtime_error(message), loc(loc_) { } Error::Error(const std::string &prefix, const Error &sub): std::runtime_error(prefix + sub.what()), loc(sub.loc) { } } rumur-2020.02.17/librumur/src/indexer.cc000066400000000000000000000164541362265074000176730ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include namespace rumur { void Indexer::visit_add(Add &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_aliasdecl(AliasDecl &n) { n.unique_id = next++; dispatch(*n.value); } void Indexer::visit_aliasrule(AliasRule &n) { n.unique_id = next++; for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void Indexer::visit_aliasstmt(AliasStmt &n) { n.unique_id = next++; for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_and(And &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_array(Array &n) { n.unique_id = next++; dispatch(*n.index_type); dispatch(*n.element_type); } void Indexer::visit_assignment(Assignment &n) { n.unique_id = next++; dispatch(*n.lhs); dispatch(*n.rhs); } void Indexer::visit_bexpr(BinaryExpr &n) { n.unique_id = next++; dispatch(*n.lhs); dispatch(*n.rhs); } void Indexer::visit_clear(Clear &n) { n.unique_id = next++; dispatch(*n.rhs); } void Indexer::visit_constdecl(ConstDecl &n) { n.unique_id = next++; dispatch(*n.value); } void Indexer::visit_div(Div &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_element(Element &n) { n.unique_id = next++; dispatch(*n.array); dispatch(*n.index); } void Indexer::visit_enum(Enum &n) { n.unique_id = next++; // allocate a block of IDs that this Enum can use for its members during type // checking (see resolve_symbols()) n.unique_id_limit = next + n.members.size(); next = n.unique_id_limit; } void Indexer::visit_eq(Eq &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_errorstmt(ErrorStmt &n) { n.unique_id = next++; } void Indexer::visit_exists(Exists &n) { n.unique_id = next++; dispatch(n.quantifier); dispatch(*n.expr); } void Indexer::visit_exprid(ExprID &n) { n.unique_id = next++; } void Indexer::visit_field(Field &n) { n.unique_id = next++; dispatch(*n.record); } void Indexer::visit_for(For &n) { n.unique_id = next++; dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_forall(Forall &n) { n.unique_id = next++; dispatch(n.quantifier); dispatch(*n.expr); } void Indexer::visit_function(Function &n) { n.unique_id = next++; for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_functioncall(FunctionCall &n) { n.unique_id = next++; for (auto &a : n.arguments) dispatch(*a); } void Indexer::visit_geq(Geq &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_gt(Gt &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_if(If &n) { n.unique_id = next++; for (IfClause &c : n.clauses) dispatch(c); } void Indexer::visit_ifclause(IfClause &n) { n.unique_id = next++; if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_implication(Implication &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_isundefined(IsUndefined &n) { n.unique_id = next++; dispatch(*n.expr); } void Indexer::visit_leq(Leq &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_lt(Lt &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_mod(Mod &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_model(Model &n) { n.unique_id = next++; for (auto &d : n.decls) dispatch(*d); for (auto &f : n.functions) dispatch(*f); for (auto &r : n.rules) dispatch(*r); } void Indexer::visit_mul(Mul &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_negative(Negative &n) { visit_uexpr(static_cast(n)); } void Indexer::visit_neq(Neq &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_not(Not &n) { visit_uexpr(static_cast(n)); } void Indexer::visit_number(Number &n) { n.unique_id = next++; } void Indexer::visit_or(Or &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_procedurecall(ProcedureCall &n) { n.unique_id = next++; dispatch(n.call); } void Indexer::visit_property(Property &n) { n.unique_id = next++; dispatch(*n.expr); } void Indexer::visit_propertyrule(PropertyRule &n) { n.unique_id = next++; for (Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void Indexer::visit_propertystmt(PropertyStmt &n) { n.unique_id = next++; dispatch(n.property); } void Indexer::visit_put(Put &n) { n.unique_id = next++; if (n.expr != nullptr) dispatch(*n.expr); } void Indexer::visit_quantifier(Quantifier &n) { n.unique_id = next++; if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); dispatch(*n.decl); } void Indexer::visit_range(Range &n) { n.unique_id = next++; dispatch(*n.min); dispatch(*n.max); } void Indexer::visit_record(Record &n) { n.unique_id = next++; for (auto &f : n.fields) dispatch(*f); } void Indexer::visit_return(Return &n) { n.unique_id = next++; if (n.expr != nullptr) dispatch(*n.expr); } void Indexer::visit_ruleset(Ruleset &n) { n.unique_id = next++; for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void Indexer::visit_scalarset(Scalarset &n) { n.unique_id = next++; dispatch(*n.bound); } void Indexer::visit_simplerule(SimpleRule &n) { n.unique_id = next++; for (Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_startstate(StartState &n) { n.unique_id = next++; for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_sub(Sub &n) { visit_bexpr(static_cast(n)); } void Indexer::visit_switch(Switch &n) { n.unique_id = next++; dispatch(*n.expr); for (SwitchCase &c : n.cases) dispatch(c); } void Indexer::visit_switchcase(SwitchCase &n) { n.unique_id = next++; for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void Indexer::visit_ternary(Ternary &n) { n.unique_id = next++; dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); } void Indexer::visit_typedecl(TypeDecl &n) { n.unique_id = next++; dispatch(*n.value); } void Indexer::visit_typeexprid(TypeExprID &n) { n.unique_id = next++; } void Indexer::visit_uexpr(UnaryExpr &n) { n.unique_id = next++; dispatch(*n.rhs); } void Indexer::visit_undefine(Undefine &n) { n.unique_id = next++; dispatch(*n.rhs); } void Indexer::visit_vardecl(VarDecl &n) { n.unique_id = next++; dispatch(*n.type); } void Indexer::visit_while(While &n) { n.unique_id = next++; dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } } rumur-2020.02.17/librumur/src/lexer.l000066400000000000000000000173221362265074000172150ustar00rootroot00000000000000%{ #include #include "parser.yy.hh" #include #include /* Override the declaration of yylex with the one from our derived scanner. */ #ifdef YY_DECL #undef YY_DECL #endif #define YY_DECL int rumur::scanner::yylex(\ rumur::parser::semantic_type *const lval, rumur::parser::location_type *loc) /* Each time yylex() is called, update the begin position with the last end * position. This ensures we get correct starting column numbers. */ #define YY_USER_ACTION loc->step(); loc->columns(yyleng); %} /* Track line numbers while scanning. These are retrievable via * FlexLexer::lineno(). */ %option yylineno /* When the end of the input is encountered, do not expect another file. */ %option noyywrap /* Generate a C++ scanner, not C. */ %option c++ /* Use our own scanner class, rather than the default yyFlexLexer. */ %option yyclass="rumur::scanner" /* Switch to case-insensitive. */ %option caseless /* States for handling comments. This is not the most efficient way of * dealing with these, but meh. */ %x SC_COMMENT SC_MULTILINE_COMMENT %% (0x[[:xdigit:]]+|[[:digit:]]+) { lval->build(YYText()); return rumur::parser::token::NUMBER; } ([[:digit:]]?\.[[:digit:]]+) { throw rumur::Error("real types are not supported", *loc); } alias { return rumur::parser::token::ALIAS; } array { return rumur::parser::token::ARRAY; } assert { return rumur::parser::token::ASSERT; } assume { return rumur::parser::token::ASSUME; } begin { return rumur::parser::token::BEGIN_TOK; } boolean { return rumur::parser::token::BOOLEAN; } by { return rumur::parser::token::BY; } case { return rumur::parser::token::CASE; } clear { return rumur::parser::token::CLEAR; } const { return rumur::parser::token::CONST; } cover { return rumur::parser::token::COVER; } do { return rumur::parser::token::DO; } else { return rumur::parser::token::ELSE; } elsif { return rumur::parser::token::ELSIF; } end { return rumur::parser::token::END; } endalias { return rumur::parser::token::ENDALIAS; } endexists { return rumur::parser::token::ENDEXISTS; } endfor { return rumur::parser::token::ENDFOR; } endforall { return rumur::parser::token::ENDFORALL; } endfunction { return rumur::parser::token::ENDFUNCTION; } endif { return rumur::parser::token::ENDIF; } endprocedure { return rumur::parser::token::ENDPROCEDURE; } endrecord { return rumur::parser::token::ENDRECORD; } endrule { return rumur::parser::token::ENDRULE; } endruleset { return rumur::parser::token::ENDRULESET; } endstartstate { return rumur::parser::token::ENDSTARTSTATE; } endswitch { return rumur::parser::token::ENDSWITCH; } endwhile { return rumur::parser::token::ENDWHILE; } enum { return rumur::parser::token::ENUM; } error { return rumur::parser::token::ERROR; } exists { return rumur::parser::token::EXISTS; } for { return rumur::parser::token::FOR; } forall { return rumur::parser::token::FORALL; } function { return rumur::parser::token::FUNCTION; } if { return rumur::parser::token::IF; } invariant { return rumur::parser::token::INVARIANT; } isundefined { return rumur::parser::token::ISUNDEFINED; } liveness { return rumur::parser::token::LIVENESS; } of { return rumur::parser::token::OF; } procedure { return rumur::parser::token::PROCEDURE; } put { return rumur::parser::token::PUT; } real { throw rumur::Error("real types are not supported", *loc); } record { return rumur::parser::token::RECORD; } return { return rumur::parser::token::RETURN; } rule { return rumur::parser::token::RULE; } ruleset { return rumur::parser::token::RULESET; } scalarset { return rumur::parser::token::SCALARSET; } startstate { return rumur::parser::token::STARTSTATE; } switch { return rumur::parser::token::SWITCH; } then { return rumur::parser::token::THEN; } to { return rumur::parser::token::TO; } type { return rumur::parser::token::TYPE; } undefine { return rumur::parser::token::UNDEFINE; } union { throw rumur::Error("union types are not supported", *loc); } var { return rumur::parser::token::VAR; } while { return rumur::parser::token::WHILE; } "∀" { return rumur::parser::token::FORALL; } "∃" { return rumur::parser::token::EXISTS; } /* Recognise true and false explicitly rather than as generic IDs (below). The * purpose of this is so that we match them case-insensitively. */ false { lval->build("false"); return rumur::parser::token::ID; } true { lval->build("true"); return rumur::parser::token::ID; } [[:alpha:]][_[:alnum:]]* { lval->build(YYText()); return rumur::parser::token::ID; } "--" { BEGIN(SC_COMMENT); } "/*" { BEGIN(SC_MULTILINE_COMMENT); } ":=" { return rumur::parser::token::COLON_EQ; } "≔" { return rumur::parser::token::COLON_EQ; } ".." { return rumur::parser::token::DOTDOT; } ">=" { return rumur::parser::token::GEQ; } "≥" { return rumur::parser::token::GEQ; } "->" { return rumur::parser::token::IMPLIES; } "→" { return rumur::parser::token::IMPLIES; } "<=" { return rumur::parser::token::LEQ; } "≤" { return rumur::parser::token::LEQ; } "!=" { return rumur::parser::token::NEQ; } "≠" { return rumur::parser::token::NEQ; } "==>" { return rumur::parser::token::ARROW; } "⇒" { return rumur::parser::token::ARROW; } "==" { return rumur::parser::token::DEQ; } "¬" { return '!'; } "∧" { return '&'; } "∨" { return '|'; } [&!|:=><\-\%\+;{},\[\]\.()/\*\?] { return YYText()[0]; } (\"|“)(\\.|[^\\\"]|\\”)*(\"|”) { std::string s(YYText()); /* figure out which quote character this string starts with */ const std::string opener = s.find("“") == 0 ? "“" : "\""; assert(s.find(opener) == 0 && "logic bug in string lexing rule"); /* figure out which quote character this string ends with */ static const std::string squote = "”"; size_t offset = s.size() - squote.size(); const std::string closer = s.size() > squote.size() && s.find(squote, offset) == offset ? squote : "\""; /* build a string, stripping the quote delimiters */ lval->build(s.substr(opener.size(), s.size() - opener.size() - closer.size()).c_str()); return rumur::parser::token::STRING; } /* Ensure we keep a correct line count for error reporting. */ "\n" { loc->lines(yyleng); loc->step(); /* maintain correct column count */ } /* Ignore white space. */ [[:space:]] { loc->step(); /* maintain correct column count */ } . { throw rumur::Error(std::string("unexpected character: '") + YYText() + "'", *loc); } /* Comment handling. */ . { /* do nothing */ } \n { loc->lines(yyleng); loc->step(); /* maintain correct column count */ BEGIN(INITIAL); } [^*\n]* { /* do nothing */ } "*"+[^*/\n]* { /* do nothing */ } \n { loc->lines(yyleng); } "*"+"/" { loc->step(); /* maintain correct column count */ BEGIN(INITIAL); } rumur-2020.02.17/librumur/src/make-version.py000077500000000000000000000063361362265074000207010ustar00rootroot00000000000000#!/usr/bin/env python3 ''' Generate contents of a version.cc. ''' import os import re import subprocess as sp import sys from typing import Optional # The version of the last tagged release of Rumur. This will be used as the # version number if no Git information is available. LAST_RELEASE = 'v2020.02.17' def has_git() -> bool: ''' Return True if we are in a Git repository and have Git. ''' # Return False if we don't have Git. try: sp.check_call(['which', 'git'], stdout=sp.DEVNULL, stderr=sp.DEVNULL) except: return False # Return False if we have no Git repository information. if not os.path.exists(os.path.join(os.path.dirname(__file__), '..', '..', '.git')): return False return True def get_tag() -> Optional[str]: ''' Find the version tag of the current Git commit, e.g. v2020.05.03, if it exists. ''' try: tag = sp.check_output(['git', 'describe', '--tags'], stderr=sp.DEVNULL) except sp.CalledProcessError: tag = None if tag is not None: tag = tag.decode('utf-8', 'replace').strip() if re.match(r'v[\d\.]+$', tag) is None: # Not a version tag. tag = None return tag def get_sha() -> str: ''' Find the hash of the current Git commit. ''' rev = sp.check_output(['git', 'rev-parse', '--verify', 'HEAD']) rev = rev.decode('utf-8', 'replace').strip() return rev def is_dirty() -> bool: ''' Determine whether the current working directory has uncommitted changes. ''' dirty = False p = sp.run(['git', 'diff', '--exit-code'], stdout=sp.DEVNULL, stderr=sp.DEVNULL) dirty |= p.returncode != 0 p = sp.Popen(['git', 'diff', '--cached', '--exit-code'], stdout=sp.DEVNULL, stderr=sp.DEVNULL) dirty |= p.returncode != 0 return dirty def main(args: [str]) -> int: if len(args) != 2 or args[1] == '--help': sys.stderr.write( f'usage: {args[0]} file\n' ' write version information as a C++ source file\n') return -1 # Get the contents of the old version file if it exists. old = None if os.path.exists(args[1]): with open(args[1], 'rt') as f: old = f.read() version = None # first, look for an environment variable that overrides other version sources version = os.environ.get('RUMUR_VERSION') # second, look for a version tag on the current commit if version is None and has_git(): tag = get_tag() if tag is not None: version = f'{tag}{" (dirty)" if is_dirty() else ""}' # third, look for the commit hash as the version if version is None and has_git(): rev = get_sha() assert rev is not None version = f'Git commit {rev}{" (dirty)" if is_dirty() else ""}' # Finally, fall back to our known release version. if version is None: version = LAST_RELEASE new = '#pragma once\n' \ '\n' \ 'namespace rumur {\n' \ '\n' \ 'static constexpr const char *get_version() {\n' \ f' return "{version}";\n' \ '}\n' \ '\n' \ '}' # If the version has changed, update the output. Otherwise we leave the old # contents -- and more importantly, the timestamp -- intact. if old != new: with open(args[1], 'wt') as f: f.write(new) return 0 if __name__ == '__main__': sys.exit(main(sys.argv)) rumur-2020.02.17/librumur/src/parse.cc000066400000000000000000000014141362265074000173350ustar00rootroot00000000000000#include #include #include #include "location.hh" #include "parser.yy.hh" #include #include #include #include #include #include namespace rumur { Ptr parse(std::istream &input) { // Setup the parser scanner s(&input); Ptr m; parser p(s, m); // Parse the input model int err = p.parse(); if (err != 0) throw Error("parsing failed", location()); // mark the global declarations for (Ptr &d : m->decls) { if (auto v = dynamic_cast(&*d)) { v->state_variable = true; } } return m; } Ptr parse(std::istream *input) { assert(input != nullptr); return parse(*input); } } rumur-2020.02.17/librumur/src/parser.yy000066400000000000000000000430201362265074000175720ustar00rootroot00000000000000%skeleton "lalr1.cc" %require "3.0" /* Ordinarily, Bison emits the parser in a namespace "yy". By changing the * namespace our parser can coexist with other Bison-generated parsers if * necessary. */ %define api.namespace {rumur} /* Make yylval, the semantic type of the current token, a variant. The * purpose of this is to allow us to return richer information about the * contents of a token or expression. */ %define api.value.type variant /* Enable more explanatory error messages. Without this, Bison just says * "syntax error" for any problem. The extra information can sometimes be * inaccurate unless you define "parse.lac full" which is not available for * C++, but even a slightly inaccurate message is typically more useful to the * user than "syntax error". */ %define parse.error verbose /* Turn on some safety checks for construction and destruction of variant * types. This is only relevant if we enable them (api.value.type variant). */ %define parse.assert /* Receive source location from the scanner. This allows us to give proper * feedback on user errors, pointing them at the line and column where they * went wrong. */ %locations /* Code that we need before anything in both the implementation * (parser.yy.cc) and the header (parser.yy.hh). */ %code requires { #include #include #include #include #include #include #include #include #include #include #include /* Forward declare the scanner class that Flex will produce for us. */ namespace rumur { class scanner; } } /* Code that we need in the implementation, but in no particular location. */ %code { #include #include #include #include #include #include #include #include #include /* Redirect yylex to call our derived scanner. */ #ifdef yylex #undef yylex #endif #define yylex sc.yylex } /* Code that we need to include at the top of the implementation * (parser.yy.cc) but not in the header. Including this code in the header * (parser.yy.hh) would induce a circular include. */ %code top { #include } /* Tell Bison that the parser receives a reference to an instance of our * scanner class, in order to use our redirected yylex defined above. */ %parse-param { scanner &sc } /* Tell Bison we'll receive another parameter that will allow us to pass * back the result of parsing. */ %parse-param { rumur::Ptr &output } %token ALIAS %token ARRAY %token ARROW %token ASSERT %token ASSUME %token BEGIN_TOK %token BOOLEAN %token BY %token CASE %token CLEAR %token COLON_EQ %token CONST %token COVER %token DEQ %token DO %token DOTDOT %token ELSE %token ELSIF %token END %token ENDALIAS %token ENDEXISTS %token ENDFOR %token ENDFORALL %token ENDFUNCTION %token ENDIF %token ENDPROCEDURE %token ENDRECORD %token ENDRULE %token ENDRULESET %token ENDSTARTSTATE %token ENDSWITCH %token ENDWHILE %token ENUM %token ERROR %token EXISTS %token FOR %token FORALL %token FUNCTION %token GEQ %token ID %token IF %token IMPLIES %token INVARIANT %token ISUNDEFINED %token LEQ %token LIVENESS %token NEQ %token NUMBER %token OF %token PROCEDURE %token PUT %token RECORD %token RETURN %token RULE %token RULESET %token SCALARSET %token STARTSTATE %token STRING %token SWITCH %token THEN %token TO %token TYPE %token UNDEFINE %token VAR %token WHILE %nonassoc '?' ':' %nonassoc IMPLIES %left '|' %left '&' %precedence '!' %nonassoc '<' LEQ DEQ '=' NEQ GEQ '>' %left '+' '-' %left '*' '/' '%' %type > aliasrule %type > category %type >> decl %type >> decls %type >> decls_header %type > designator %type > else_opt %type > elsifs %type > expr %type , rumur::location>>> exprdecl %type , rumur::location>>> exprdecls %type >> exprlist %type >> exprlist_cont %type > guard_opt %type >> id_list %type >> id_list_opt %type >> parameter %type >> parameters %type > procdecl %type >> procdecls %type > property %type > quantifier %type > quantifiers %type > return_type %type > rule %type > ruleset %type >> rules %type > simplerule %type > startstate %type > stmt %type >> stmts %type >> stmts_cont %type string_opt %type > switchcases %type > switchcases_cont %type >> typedecl %type >> typedecls %type > typeexpr %type >> vardecl %type >> vardecls %type > var_opt %% model: decls procdecls rules { output = rumur::Ptr::make($1, $2, $3, @$); }; aliasrule: ALIAS exprdecls DO rules endalias { std::vector> decls; for (const std::tuple, rumur::location> &d : $2) { decls.push_back(rumur::Ptr::make(std::get<0>(d), std::get<1>(d), std::get<2>(d))); } $$ = rumur::Ptr::make(decls, $4, @$); }; begin_opt: BEGIN_TOK | %empty; category: ASSERT { $$ = std::make_shared(rumur::Property::ASSERTION); } | ASSUME { $$ = std::make_shared(rumur::Property::ASSUMPTION); } | COVER { $$ = std::make_shared(rumur::Property::COVER); } | INVARIANT { $$ = std::make_shared(rumur::Property::ASSERTION); } | LIVENESS { $$ = std::make_shared(rumur::Property::LIVENESS); }; comma_opt: ',' | %empty; decl: CONST exprdecls { for (const std::tuple, rumur::location> &d : $2) { $$.push_back(rumur::Ptr::make(std::get<0>(d), std::get<1>(d), std::get<2>(d))); } } | TYPE typedecls { $$ = $2; } | VAR vardecls { std::move($2.begin(), $2.end(), std::back_inserter($$)); }; decls: decls decl { $$ = $1; std::move($2.begin(), $2.end(), std::back_inserter($$)); } | %empty { /* nothing required */ }; decls_header: decls BEGIN_TOK { $$ = $1; } | %empty { }; designator: designator '.' ID { $$ = rumur::Ptr::make($1, $3, @$); } | designator '[' expr ']' { $$ = rumur::Ptr::make($1, $3, @$); } | ID { $$ = rumur::Ptr::make($1, nullptr, @$); }; else_opt: ELSE stmts { $$.push_back(rumur::IfClause(nullptr, $2, @$)); } | %empty { }; elsifs: elsifs ELSIF expr THEN stmts { $$ = $1; $$.push_back(rumur::IfClause($3, $5, rumur::location(@2.begin, @5.end))); } | %empty { }; endalias: END | ENDALIAS; endexists: END | ENDEXISTS; endfor: END | ENDFOR; endforall: END | ENDFORALL; endfunction: END | ENDFUNCTION | ENDPROCEDURE; endif: END | ENDIF; endrecord: END | ENDRECORD; endrule: END | ENDRULE; endruleset: END | ENDRULESET; endstartstate: END | ENDSTARTSTATE; endswitch: END | ENDSWITCH; endwhile: END | ENDWHILE; expr: expr '?' expr ':' expr { $$ = rumur::Ptr::make($1, $3, $5, @$); } | expr IMPLIES expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '|' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '&' expr { $$ = rumur::Ptr::make($1, $3, @$); } | '!' expr { $$ = rumur::Ptr::make($2, @$); } | expr '<' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr LEQ expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '>' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr GEQ expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr DEQ expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '=' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr NEQ expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '+' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '-' expr { $$ = rumur::Ptr::make($1, $3, @$); } | '+' expr %prec '*' { $$ = $2; $$->loc = @$; } | '-' expr %prec '*' { $$ = rumur::Ptr::make($2, @$); } | expr '*' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '/' expr { $$ = rumur::Ptr::make($1, $3, @$); } | expr '%' expr { $$ = rumur::Ptr::make($1, $3, @$); } | FORALL quantifier DO expr endforall { $$ = rumur::Ptr::make(*$2, $4, @$); } | EXISTS quantifier DO expr endexists { $$ = rumur::Ptr::make(*$2, $4, @$); } | designator { $$ = $1; } | NUMBER { $$ = rumur::Ptr::make($1, @$); } | '(' expr ')' { $$ = $2; $$->loc = @$; } | ID '(' exprlist ')' { $$ = rumur::Ptr::make($1, $3, @$); } | ISUNDEFINED '(' designator ')' { $$ = rumur::Ptr::make($3, @$); }; exprdecl: id_list_opt ':' expr { for (const std::pair &m : $1) { $$.push_back(std::make_tuple(m.first, $3, @$)); } }; exprdecls: exprdecls exprdecl semi_opt { $$ = $1; std::move($2.begin(), $2.end(), std::back_inserter($$)); } | %empty { /* nothing required */ }; exprlist: exprlist_cont expr comma_opt { $$ = $1; $$.push_back($2); } | %empty { }; exprlist_cont: exprlist_cont expr ',' { $$ = $1; $$.push_back($2); } | %empty { }; function: FUNCTION | PROCEDURE; guard_opt: expr ARROW { $$ = $1; } | %empty { $$ = nullptr; }; id_list: id_list ',' ID { $$ = $1; $$.emplace_back(std::make_pair($3, @3)); } | ID { $$.emplace_back(std::make_pair($1, @$)); }; /* Support optional trailing comma to make it easier for tools that generate * an input mdoels. */ id_list_opt: id_list comma_opt { $$ = $1; } | %empty { }; parameter: var_opt id_list ':' typeexpr { for (const std::pair &i : $2) { auto v = rumur::Ptr::make(i.first, $4, @$); v->readonly = !*$1; $$.push_back(v); } }; parameters: parameters parameter semi_opt { $$ = $1; std::move($2.begin(), $2.end(), std::back_inserter($$)); } | %empty { }; procdecl: function ID '(' parameters ')' return_type decls begin_opt stmts endfunction semi_opt { $$ = rumur::Ptr::make($2, $4, $6, $7, $9, @$); }; procdecls: procdecls procdecl { $$ = $1; $$.push_back($2); } | %empty { }; property: category STRING expr { rumur::Property p(*$1, $3, @3); $$ = rumur::Ptr::make($2, p, @$); } | category expr string_opt { rumur::Property p(*$1, $2, @2); $$ = rumur::Ptr::make($3, p, @$); }; quantifier: ID ':' typeexpr { $$ = std::make_shared($1, $3, @$); } | ID COLON_EQ expr TO expr BY expr { $$ = std::make_shared($1, $3, $5, $7, @$); } | ID COLON_EQ expr TO expr { $$ = std::make_shared($1, $3, $5, @$); }; quantifiers: quantifiers semis quantifier { $$ = $1; $$.push_back(*$3); } | quantifier { $$.push_back(*$1); }; return_type: ':' typeexpr semi_opt { $$ = $2; } | semi_opt { $$ = nullptr; }; rule: startstate { $$ = $1; } | simplerule { $$ = $1; } | property { $$ = $1; } | ruleset { $$ = $1; } | aliasrule { $$ = $1; }; rules: rules rule semi_opt { $$ = $1; $$.push_back($2); } | %empty { }; ruleset: RULESET quantifiers DO rules endruleset { $$ = rumur::Ptr::make($2, $4, @$); }; semi_opt: semi_opt ';' | %empty; semis: semi_opt ';'; simplerule: RULE string_opt guard_opt decls_header stmts endrule { $$ = rumur::Ptr::make($2, $3, $4, $5, @$); }; startstate: STARTSTATE string_opt decls_header stmts endstartstate { $$ = rumur::Ptr::make($2, $3, $4, @$); }; stmt: category STRING expr { rumur::Property p(*$1, $3, @3); $$ = rumur::Ptr::make(p, $2, @$); } | category expr string_opt { rumur::Property p(*$1, $2, @2); $$ = rumur::Ptr::make(p, $3, @$); } | designator COLON_EQ expr { $$ = rumur::Ptr::make($1, $3, @$); } | ALIAS exprdecls DO stmts endalias { std::vector> decls; for (const std::tuple, rumur::location> &d : $2) { decls.push_back(rumur::Ptr::make(std::get<0>(d), std::get<1>(d), std::get<2>(d))); } $$ = rumur::Ptr::make(decls, $4, @$); } | ERROR STRING { $$ = rumur::Ptr::make($2, @$); } | CLEAR designator { $$ = rumur::Ptr::make($2, @$); } | FOR quantifier DO stmts endfor { $$ = rumur::Ptr::make(*$2, $4, @$); } | IF expr THEN stmts elsifs else_opt endif { std::vector cs = { rumur::IfClause($2, $4, rumur::location(@1.begin, @4.end)) }; cs.insert(cs.end(), $5.begin(), $5.end()); cs.insert(cs.end(), $6.begin(), $6.end()); $$ = rumur::Ptr::make(cs, @$); } | PUT STRING { $$ = rumur::Ptr::make($2, @$); } | PUT expr { $$ = rumur::Ptr::make($2, @$); } | RETURN { $$ = rumur::Ptr::make(nullptr, @$); } | RETURN expr { $$ = rumur::Ptr::make($2, @$); } | UNDEFINE designator { $$ = rumur::Ptr::make($2, @$); } | ID '(' exprlist ')' { $$ = rumur::Ptr::make($1, $3, @$); } | WHILE expr DO stmts endwhile { $$ = rumur::Ptr::make($2, $4, @$); } | SWITCH expr switchcases endswitch { $$ = rumur::Ptr::make($2, $3, @$); }; stmts: stmts_cont stmt semi_opt { $$ = $1; $$.push_back($2); } | stmt semi_opt { $$.push_back($1); } | %empty { }; stmts_cont: stmts_cont stmt semis { $$ = $1; $$.push_back($2); } | stmt semis { $$.push_back($1); }; string_opt: STRING { $$ = $1; } | %empty { /* nothing required */ }; switchcases: switchcases_cont ELSE stmts { $$ = $1; $$.push_back(rumur::SwitchCase(std::vector>(), $3, @$)); } | switchcases_cont { $$ = $1; }; switchcases_cont: switchcases_cont CASE exprlist ':' stmts { $$ = $1; $$.push_back(rumur::SwitchCase($3, $5, @$)); } | %empty { /* nothing required */ }; typedecl: id_list_opt ':' typeexpr { for (const std::pair &m : $1) { $$.push_back(rumur::Ptr::make(m.first, $3, @$)); } }; typedecls: typedecls typedecl semi_opt { $$ = $1; std::move($2.begin(), $2.end(), std::back_inserter($$)); } | %empty { /* nothing required */ }; typeexpr: BOOLEAN { /* We need to special case this instead of just using the ID rule because IDs * are treated as case-sensitive while "boolean" is not. To avoid awkwardness * in later symbol resolution, we force it to lower case here. */ $$ = rumur::Ptr::make("boolean", nullptr, @$); } | ID { $$ = rumur::Ptr::make($1, nullptr, @$); } | expr DOTDOT expr { $$ = rumur::Ptr::make($1, $3, @$); } | ENUM '{' id_list_opt '}' { $$ = rumur::Ptr::make($3, @$); } | RECORD vardecls endrecord { $$ = rumur::Ptr::make($2, @$); } | ARRAY '[' typeexpr ']' OF typeexpr { $$ = rumur::Ptr::make($3, $6, @$); } | SCALARSET '(' expr ')' { $$ = rumur::Ptr::make($3, @$); }; vardecl: id_list_opt ':' typeexpr { for (const std::pair &m : $1) { $$.push_back(rumur::Ptr::make(m.first, $3, @$)); } }; vardecls: vardecls vardecl semi_opt { $$ = $1; std::move($2.begin(), $2.end(), std::back_inserter($$)); } | %empty { /* nothing required */ }; var_opt: VAR { $$ = std::make_shared(true); } | %empty { $$ = std::make_shared(false); }; %% void rumur::parser::error(const location_type &loc, const std::string &message) { throw Error(message, loc); } rumur-2020.02.17/librumur/src/resolve-symbols.cc000066400000000000000000000171311362265074000213730ustar00rootroot00000000000000#include #include #include "location.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { namespace { class Resolver : public Traversal { private: Symtab symtab; public: Resolver() { // Open a global scope symtab.open_scope(); // Teach the symbol table the built ins auto td = Ptr::make("boolean", Boolean, location()); symtab.declare("boolean", td); mpz_class index = 0; for (const std::pair &m : Boolean->members) { symtab.declare(m.first, Ptr::make("boolean", Ptr::make(index, location()), Boolean, location())); index++; } } void visit_aliasrule(AliasRule &n) final { symtab.open_scope(); for (auto &a : n.aliases) { dispatch(*a); symtab.declare(a->name, a); } for (auto &r : n.rules) dispatch(*r); symtab.close_scope(); } void visit_aliasstmt(AliasStmt &n) final { symtab.open_scope(); for (auto &a : n.aliases) { dispatch(*a); symtab.declare(a->name, a); } for (auto &s : n.body) dispatch(*s); symtab.close_scope(); } void visit_enum(Enum &n) final { auto e = Ptr::make(n); // register all the enum members so they can be referenced later mpz_class index = 0; size_t id = e->unique_id + 1; for (const std::pair &m : n.members) { auto cd = Ptr::make(m.first, Ptr::make(index, m.second), e, m.second); // assign this member a unique id so that referrers can use it if need be assert(id < e->unique_id_limit && "number of enum members exceeds what " "was expected"); cd->unique_id = id; symtab.declare(m.first, cd); index++; id++; } } void visit_exists(Exists &n) final { symtab.open_scope(); dispatch(n.quantifier); dispatch(*n.expr); symtab.close_scope(); } void visit_exprid(ExprID &n) final { if (n.value == nullptr) { // This reference is unresolved Ptr d = symtab.lookup(n.id, n.loc); if (d == nullptr) throw Error("unknown symbol \"" + n.id + "\"", n.loc); n.value = d; } } void visit_for(For &n) final { symtab.open_scope(); dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); symtab.close_scope(); } void visit_forall(Forall &n) final { symtab.open_scope(); dispatch(n.quantifier); dispatch(*n.expr); symtab.close_scope(); } void visit_function(Function &n) final { symtab.open_scope(); for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); // register the function itself, even though its body has not yet been // resolved, in order to allow contained function calls to resolve to the // containing function, supporting recursion symtab.declare(n.name, Ptr::make(n)); // only register the function parameters now, to avoid their names shadowing // anything that needs to be resolved during symbol resolution of another // parameter or the return type for (auto &p : n.parameters) symtab.declare(p->name, p); for (auto &d : n.decls) { dispatch(*d); symtab.declare(d->name, d); } for (auto &s : n.body) dispatch(*s); symtab.close_scope(); } void visit_functioncall(FunctionCall &n) final { if (n.function == nullptr) { // This reference is unresolved Ptr f = symtab.lookup(n.name, n.loc); if (f == nullptr) throw Error("unknown function call \"" + n.name + "\"", n.loc); n.function = f; } for (auto &a : n.arguments) dispatch(*a); } void visit_model(Model &n) final { // running marker of offset in the global state data mpz_class offset = 0; /* whether we have not yet hit any problems that make offset calculation * impossible */ bool ok = true; for (auto &d : n.decls) { dispatch(*d); /* if this was a variable declaration, we now know enough to determine its * offset in the global state data */ if (ok) { if (auto v = dynamic_cast(d.get())) { /* If the declaration or one of its children does not validate, it is * unsafe to call width(). */ try { validate(*v); } catch (Error&) { /* Skip this and future offset calculations and assume our caller * will eventually discover the underlying reason when they call * n.validate(). */ ok = false; } if (ok) { v->offset = offset; offset += v->type->width(); } } } symtab.declare(d->name, d); } for (auto &f : n.functions) { dispatch(*f); symtab.declare(f->name, f); } for (auto &r : n.rules) dispatch(*r); } void visit_quantifier(Quantifier &n) final { if (n.type != nullptr) { // wrap symbol resolution within the type in a dummy scope to suppress any // declarations (primarily enum members) as these will be duplicated in // when we descend into decl below symtab.open_scope(); dispatch(*n.type); symtab.close_scope(); } if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); // if the bounds for this iteration are now known to be constant, we can // narrow its VarDecl if (n.from != nullptr && n.from->constant() && n.to != nullptr && n.to->constant()) { auto r = dynamic_cast(*n.decl->type); // the range may have been given as either an up count or down count if (n.from->constant_fold() <= n.to->constant_fold()) { r.min = n.from; r.max = n.to; } else { r.min = n.to; r.max = n.from; } } dispatch(*n.decl); symtab.declare(n.name, n.decl); } void visit_ruleset(Ruleset &n) final { symtab.open_scope(); for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); symtab.close_scope(); } void visit_simplerule(SimpleRule &n) final { symtab.open_scope(); for (Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) { dispatch(*d); symtab.declare(d->name, d); } for (auto &s : n.body) dispatch(*s); symtab.close_scope(); } void visit_startstate(StartState &n) final { symtab.open_scope(); for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) { dispatch(*d); symtab.declare(d->name, d); } for (auto &s : n.body) dispatch(*s); symtab.close_scope(); } void visit_typeexprid(TypeExprID &n) final { if (n.referent == nullptr) { // This reference is unresolved Ptr t = symtab.lookup(n.name, n.loc); if (t == nullptr) throw Error("unknown type symbol \"" + n.name + "\"", n.loc); n.referent = t; } } virtual ~Resolver() = default; }; } void resolve_symbols(Model &m) { Resolver r; r.dispatch(m); } } rumur-2020.02.17/librumur/src/traverse.cc000066400000000000000000001132701362265074000200620ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { void BaseTraversal::dispatch(Node &n) { if (auto i = dynamic_cast(&n)) { visit_add(*i); return; } if (auto i = dynamic_cast(&n)) { visit_aliasdecl(*i); return; } if (auto i = dynamic_cast(&n)) { visit_aliasrule(*i); return; } if (auto i = dynamic_cast(&n)) { visit_aliasstmt(*i); return; } if (auto i = dynamic_cast(&n)) { visit_and(*i); return; } if (auto i = dynamic_cast(&n)) { visit_array(*i); return; } if (auto i = dynamic_cast(&n)) { visit_assignment(*i); return; } if (auto i = dynamic_cast(&n)) { visit_constdecl(*i); return; } if (auto i = dynamic_cast(&n)) { visit_clear(*i); return; } if (auto i = dynamic_cast(&n)) { visit_div(*i); return; } if (auto i = dynamic_cast(&n)) { visit_element(*i); return; } if (auto i = dynamic_cast(&n)) { visit_function(*i); return; } if (auto i = dynamic_cast(&n)) { visit_functioncall(*i); return; } if (auto i = dynamic_cast(&n)) { visit_enum(*i); return; } if (auto i = dynamic_cast(&n)) { visit_eq(*i); return; } if (auto i = dynamic_cast(&n)) { visit_errorstmt(*i); return; } if (auto i = dynamic_cast(&n)) { visit_exists(*i); return; } if (auto i = dynamic_cast(&n)) { visit_exprid(*i); return; } if (auto i = dynamic_cast(&n)) { visit_field(*i); return; } if (auto i = dynamic_cast(&n)) { visit_for(*i); return; } if (auto i = dynamic_cast(&n)) { visit_forall(*i); return; } if (auto i = dynamic_cast(&n)) { visit_geq(*i); return; } if (auto i = dynamic_cast(&n)) { visit_gt(*i); return; } if (auto i = dynamic_cast(&n)) { visit_if(*i); return; } if (auto i = dynamic_cast(&n)) { visit_ifclause(*i); return; } if (auto i = dynamic_cast(&n)) { visit_implication(*i); return; } if (auto i = dynamic_cast(&n)) { visit_isundefined(*i); return; } if (auto i = dynamic_cast(&n)) { visit_leq(*i); return; } if (auto i = dynamic_cast(&n)) { visit_lt(*i); return; } if (auto i = dynamic_cast(&n)) { visit_model(*i); return; } if (auto i = dynamic_cast(&n)) { visit_mod(*i); return; } if (auto i = dynamic_cast(&n)) { visit_mul(*i); return; } if (auto i = dynamic_cast(&n)) { visit_negative(*i); return; } if (auto i = dynamic_cast(&n)) { visit_neq(*i); return; } if (auto i = dynamic_cast(&n)) { visit_not(*i); return; } if (auto i = dynamic_cast(&n)) { visit_number(*i); return; } if (auto i = dynamic_cast(&n)) { visit_or(*i); return; } if (auto i = dynamic_cast(&n)) { visit_procedurecall(*i); return; } if (auto i = dynamic_cast(&n)) { visit_property(*i); return; } if (auto i = dynamic_cast(&n)) { visit_propertyrule(*i); return; } if (auto i = dynamic_cast(&n)) { visit_propertystmt(*i); return; } if (auto i = dynamic_cast(&n)) { visit_put(*i); return; } if (auto i = dynamic_cast(&n)) { visit_quantifier(*i); return; } if (auto i = dynamic_cast(&n)) { visit_range(*i); return; } if (auto i = dynamic_cast(&n)) { visit_record(*i); return; } if (auto i = dynamic_cast(&n)) { visit_return(*i); return; } if (auto i = dynamic_cast(&n)) { visit_ruleset(*i); return; } if (auto i = dynamic_cast(&n)) { visit_scalarset(*i); return; } if (auto i = dynamic_cast(&n)) { visit_simplerule(*i); return; } if (auto i = dynamic_cast(&n)) { visit_startstate(*i); return; } if (auto i = dynamic_cast(&n)) { visit_sub(*i); return; } if (auto i = dynamic_cast(&n)) { visit_switch(*i); return; } if (auto i = dynamic_cast(&n)) { visit_switchcase(*i); return; } if (auto i = dynamic_cast(&n)) { visit_ternary(*i); return; } if (auto i = dynamic_cast(&n)) { visit_typedecl(*i); return; } if (auto i = dynamic_cast(&n)) { visit_typeexprid(*i); return; } if (auto i = dynamic_cast(&n)) { visit_undefine(*i); return; } if (auto i = dynamic_cast(&n)) { visit_vardecl(*i); return; } if (auto i = dynamic_cast(&n)) { visit_while(*i); return; } #ifndef NDEBUG std::cerr << "missed case in BaseTraversal::dispatch: " << typeid(n).name() << "\n"; #endif assert(!"missed case in BaseTraversal::dispatch"); } void Traversal::visit_add(Add &n) { visit_bexpr(n); } void Traversal::visit_aliasdecl(AliasDecl &n) { dispatch(*n.value); } void Traversal::visit_aliasrule(AliasRule &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void Traversal::visit_aliasstmt(AliasStmt &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_and(And &n) { visit_bexpr(n); } void Traversal::visit_array(Array &n) { dispatch(*n.index_type); dispatch(*n.element_type); } void Traversal::visit_assignment(Assignment &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void Traversal::visit_bexpr(BinaryExpr &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void Traversal::visit_clear(Clear &n) { dispatch(*n.rhs); } void Traversal::visit_constdecl(ConstDecl &n) { dispatch(*n.value); } void Traversal::visit_div(Div &n) { visit_bexpr(n); } void Traversal::visit_element(Element &n) { dispatch(*n.array); dispatch(*n.index); } void Traversal::visit_enum(Enum&) { } void Traversal::visit_eq(Eq &n) { visit_bexpr(n); } void Traversal::visit_errorstmt(ErrorStmt&) { } void Traversal::visit_exists(Exists &n) { dispatch(n.quantifier); dispatch(*n.expr); } void Traversal::visit_exprid(ExprID&) { } void Traversal::visit_field(Field &n) { dispatch(*n.record); } void Traversal::visit_for(For &n) { dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_forall(Forall &n) { dispatch(n.quantifier); dispatch(*n.expr); } void Traversal::visit_function(Function &n) { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_functioncall(FunctionCall &n) { for (auto &a : n.arguments) dispatch(*a); } void Traversal::visit_geq(Geq &n) { visit_bexpr(n); } void Traversal::visit_gt(Gt &n) { visit_bexpr(n); } void Traversal::visit_if(If &n) { for (IfClause &c : n.clauses) dispatch(c); } void Traversal::visit_ifclause(IfClause &n) { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_implication(Implication &n) { visit_bexpr(n); } void Traversal::visit_isundefined(IsUndefined &n) { dispatch(*n.expr); } void Traversal::visit_leq(Leq &n) { visit_bexpr(n); } void Traversal::visit_lt(Lt &n) { visit_bexpr(n); } void Traversal::visit_mod(Mod &n) { visit_bexpr(n); } void Traversal::visit_model(Model &n) { for (auto &d : n.decls) dispatch(*d); for (auto &f : n.functions) dispatch(*f); for (auto &r : n.rules) dispatch(*r); } void Traversal::visit_mul(Mul &n) { visit_bexpr(n); } void Traversal::visit_negative(Negative &n) { visit_uexpr(n); } void Traversal::visit_neq(Neq &n) { visit_bexpr(n); } void Traversal::visit_not(Not &n) { visit_uexpr(n); } void Traversal::visit_number(Number&) { } void Traversal::visit_or(Or &n) { visit_bexpr(n); } void Traversal::visit_procedurecall(ProcedureCall &n) { dispatch(n.call); } void Traversal::visit_property(Property &n) { dispatch(*n.expr); } void Traversal::visit_propertyrule(PropertyRule &n) { for (Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void Traversal::visit_propertystmt(PropertyStmt &n) { dispatch(n.property); } void Traversal::visit_put(Put &n) { if (n.expr != nullptr) dispatch(*n.expr); } void Traversal::visit_quantifier(Quantifier &n) { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); } void Traversal::visit_range(Range &n) { dispatch(*n.min); dispatch(*n.max); } void Traversal::visit_record(Record &n) { for (auto &f : n.fields) dispatch(*f); } void Traversal::visit_return(Return &n) { if (n.expr != nullptr) dispatch(*n.expr); } void Traversal::visit_ruleset(Ruleset &n) { for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void Traversal::visit_scalarset(Scalarset &n) { dispatch(*n.bound); } void Traversal::visit_simplerule(SimpleRule &n) { for (Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_startstate(StartState &n) { for (Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_sub(Sub &n) { visit_bexpr(n); } void Traversal::visit_switch(Switch &n) { dispatch(*n.expr); for (SwitchCase &c : n.cases) dispatch(c); } void Traversal::visit_switchcase(SwitchCase &n) { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void Traversal::visit_ternary(Ternary &n) { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); } void Traversal::visit_typedecl(TypeDecl &n) { dispatch(*n.value); } void Traversal::visit_typeexprid(TypeExprID&) { } void Traversal::visit_uexpr(UnaryExpr &n) { dispatch(*n.rhs); } void Traversal::visit_undefine(Undefine &n) { dispatch(*n.rhs); } void Traversal::visit_vardecl(VarDecl &n) { if (n.type != nullptr) dispatch(*n.type); } void Traversal::visit_while(While &n) { dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } Traversal::~Traversal() { } void ConstBaseTraversal::dispatch(const Node &n) { if (auto i = dynamic_cast(&n)) { visit_add(*i); return; } if (auto i = dynamic_cast(&n)) { visit_aliasdecl(*i); return; } if (auto i = dynamic_cast(&n)) { visit_aliasrule(*i); return; } if (auto i = dynamic_cast(&n)) { visit_aliasstmt(*i); return; } if (auto i = dynamic_cast(&n)) { visit_and(*i); return; } if (auto i = dynamic_cast(&n)) { visit_array(*i); return; } if (auto i = dynamic_cast(&n)) { visit_assignment(*i); return; } if (auto i = dynamic_cast(&n)) { visit_clear(*i); return; } if (auto i = dynamic_cast(&n)) { visit_constdecl(*i); return; } if (auto i = dynamic_cast(&n)) { visit_div(*i); return; } if (auto i = dynamic_cast(&n)) { visit_element(*i); return; } if (auto i = dynamic_cast(&n)) { visit_enum(*i); return; } if (auto i = dynamic_cast(&n)) { visit_eq(*i); return; } if (auto i = dynamic_cast(&n)) { visit_errorstmt(*i); return; } if (auto i = dynamic_cast(&n)) { visit_exists(*i); return; } if (auto i = dynamic_cast(&n)) { visit_exprid(*i); return; } if (auto i = dynamic_cast(&n)) { visit_field(*i); return; } if (auto i = dynamic_cast(&n)) { visit_for(*i); return; } if (auto i = dynamic_cast(&n)) { visit_forall(*i); return; } if (auto i = dynamic_cast(&n)) { visit_function(*i); return; } if (auto i = dynamic_cast(&n)) { visit_functioncall(*i); return; } if (auto i = dynamic_cast(&n)) { visit_geq(*i); return; } if (auto i = dynamic_cast(&n)) { visit_gt(*i); return; } if (auto i = dynamic_cast(&n)) { visit_if(*i); return; } if (auto i = dynamic_cast(&n)) { visit_ifclause(*i); return; } if (auto i = dynamic_cast(&n)) { visit_implication(*i); return; } if (auto i = dynamic_cast(&n)) { visit_isundefined(*i); return; } if (auto i = dynamic_cast(&n)) { visit_leq(*i); return; } if (auto i = dynamic_cast(&n)) { visit_lt(*i); return; } if (auto i = dynamic_cast(&n)) { visit_model(*i); return; } if (auto i = dynamic_cast(&n)) { visit_mod(*i); return; } if (auto i = dynamic_cast(&n)) { visit_mul(*i); return; } if (auto i = dynamic_cast(&n)) { visit_negative(*i); return; } if (auto i = dynamic_cast(&n)) { visit_neq(*i); return; } if (auto i = dynamic_cast(&n)) { visit_not(*i); return; } if (auto i = dynamic_cast(&n)) { visit_number(*i); return; } if (auto i = dynamic_cast(&n)) { visit_or(*i); return; } if (auto i = dynamic_cast(&n)) { visit_procedurecall(*i); return; } if (auto i = dynamic_cast(&n)) { visit_property(*i); return; } if (auto i = dynamic_cast(&n)) { visit_propertyrule(*i); return; } if (auto i = dynamic_cast(&n)) { visit_propertystmt(*i); return; } if (auto i = dynamic_cast(&n)) { visit_put(*i); return; } if (auto i = dynamic_cast(&n)) { visit_quantifier(*i); return; } if (auto i = dynamic_cast(&n)) { visit_range(*i); return; } if (auto i = dynamic_cast(&n)) { visit_record(*i); return; } if (auto i = dynamic_cast(&n)) { visit_return(*i); return; } if (auto i = dynamic_cast(&n)) { visit_ruleset(*i); return; } if (auto i = dynamic_cast(&n)) { visit_scalarset(*i); return; } if (auto i = dynamic_cast(&n)) { visit_simplerule(*i); return; } if (auto i = dynamic_cast(&n)) { visit_startstate(*i); return; } if (auto i = dynamic_cast(&n)) { visit_sub(*i); return; } if (auto i = dynamic_cast(&n)) { visit_switch(*i); return; } if (auto i = dynamic_cast(&n)) { visit_switchcase(*i); return; } if (auto i = dynamic_cast(&n)) { visit_ternary(*i); return; } if (auto i = dynamic_cast(&n)) { visit_typedecl(*i); return; } if (auto i = dynamic_cast(&n)) { visit_typeexprid(*i); return; } if (auto i = dynamic_cast(&n)) { visit_undefine(*i); return; } if (auto i = dynamic_cast(&n)) { visit_vardecl(*i); return; } if (auto i = dynamic_cast(&n)) { visit_while(*i); return; } #ifndef NDEBUG std::cerr << "missed case in ConstBaseTraversal::dispatch: " << typeid(n).name() << "\n"; #endif assert(!"missed case in ConstBaseTraversal::dispatch"); } void ConstTraversal::visit_add(const Add &n) { visit_bexpr(n); } void ConstTraversal::visit_aliasdecl(const AliasDecl &n) { dispatch(*n.value); } void ConstTraversal::visit_aliasrule(const AliasRule &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void ConstTraversal::visit_aliasstmt(const AliasStmt &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_and(const And &n) { visit_bexpr(n); } void ConstTraversal::visit_array(const Array &n) { dispatch(*n.index_type); dispatch(*n.element_type); } void ConstTraversal::visit_assignment(const Assignment &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTraversal::visit_bexpr(const BinaryExpr &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTraversal::visit_clear(const Clear &n) { dispatch(*n.rhs); } void ConstTraversal::visit_constdecl(const ConstDecl &n) { dispatch(*n.value); } void ConstTraversal::visit_div(const Div &n) { visit_bexpr(n); } void ConstTraversal::visit_element(const Element &n) { dispatch(*n.array); dispatch(*n.index); } void ConstTraversal::visit_enum(const Enum&) { } void ConstTraversal::visit_eq(const Eq &n) { visit_bexpr(n); } void ConstTraversal::visit_errorstmt(const ErrorStmt&) { } void ConstTraversal::visit_exists(const Exists &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstTraversal::visit_exprid(const ExprID&) { } void ConstTraversal::visit_field(const Field &n) { dispatch(*n.record); } void ConstTraversal::visit_for(const For &n) { dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_forall(const Forall &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstTraversal::visit_function(const Function &n) { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_functioncall(const FunctionCall &n) { for (auto &a : n.arguments) dispatch(*a); } void ConstTraversal::visit_geq(const Geq &n) { visit_bexpr(n); } void ConstTraversal::visit_gt(const Gt &n) { visit_bexpr(n); } void ConstTraversal::visit_if(const If &n) { for (const IfClause &c : n.clauses) dispatch(c); } void ConstTraversal::visit_ifclause(const IfClause &n) { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_implication(const Implication &n) { visit_bexpr(n); } void ConstTraversal::visit_isundefined(const IsUndefined &n) { dispatch(*n.expr); } void ConstTraversal::visit_leq(const Leq &n) { visit_bexpr(n); } void ConstTraversal::visit_lt(const Lt &n) { visit_bexpr(n); } void ConstTraversal::visit_mod(const Mod &n) { visit_bexpr(n); } void ConstTraversal::visit_model(const Model &n) { for (auto &d : n.decls) dispatch(*d); for (auto &f : n.functions) dispatch(*f); for (auto &r : n.rules) dispatch(*r); } void ConstTraversal::visit_mul(const Mul &n) { visit_bexpr(n); } void ConstTraversal::visit_negative(const Negative &n) { visit_uexpr(n); } void ConstTraversal::visit_neq(const Neq &n) { visit_bexpr(n); } void ConstTraversal::visit_not(const Not &n) { visit_uexpr(n); } void ConstTraversal::visit_number(const Number&) { } void ConstTraversal::visit_or(const Or &n) { visit_bexpr(n); } void ConstTraversal::visit_procedurecall(const ProcedureCall &n) { dispatch(n.call); } void ConstTraversal::visit_property(const Property &n) { dispatch(*n.expr); } void ConstTraversal::visit_propertyrule(const PropertyRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void ConstTraversal::visit_propertystmt(const PropertyStmt &n) { dispatch(n.property); } void ConstTraversal::visit_put(const Put &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstTraversal::visit_quantifier(const Quantifier &n) { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); } void ConstTraversal::visit_range(const Range &n) { dispatch(*n.min); dispatch(*n.max); } void ConstTraversal::visit_record(const Record &n) { for (auto &f : n.fields) dispatch(*f); } void ConstTraversal::visit_return(const Return &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstTraversal::visit_ruleset(const Ruleset &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void ConstTraversal::visit_scalarset(const Scalarset &n) { dispatch(*n.bound); } void ConstTraversal::visit_simplerule(const SimpleRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_startstate(const StartState &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_sub(const Sub &n) { visit_bexpr(n); } void ConstTraversal::visit_switch(const Switch &n) { dispatch(*n.expr); for (const SwitchCase &c : n.cases) dispatch(c); } void ConstTraversal::visit_switchcase(const SwitchCase &n) { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void ConstTraversal::visit_ternary(const Ternary &n) { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTraversal::visit_typedecl(const TypeDecl &n) { dispatch(*n.value); } void ConstTraversal::visit_typeexprid(const TypeExprID&) { } void ConstTraversal::visit_uexpr(const UnaryExpr &n) { dispatch(*n.rhs); } void ConstTraversal::visit_undefine(const Undefine &n) { dispatch(*n.rhs); } void ConstTraversal::visit_vardecl(const VarDecl &n) { if (n.type != nullptr) dispatch(*n.type); } void ConstTraversal::visit_while(const While &n) { dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } ConstTraversal::~ConstTraversal() { } void ConstExprTraversal::visit_aliasdecl(const AliasDecl &n) { dispatch(*n.value); } void ConstExprTraversal::visit_aliasrule(const AliasRule &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void ConstExprTraversal::visit_aliasstmt(const AliasStmt &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_array(const Array &n) { dispatch(*n.index_type); dispatch(*n.element_type); } void ConstExprTraversal::visit_assignment(const Assignment &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstExprTraversal::visit_clear(const Clear &n) { dispatch(*n.rhs); } void ConstExprTraversal::visit_constdecl(const ConstDecl &n) { dispatch(*n.value); } void ConstExprTraversal::visit_enum(const Enum&) { } void ConstExprTraversal::visit_errorstmt(const ErrorStmt&) { } void ConstExprTraversal::visit_for(const For &n) { dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_function(const Function &n) { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_if(const If &n) { for (const IfClause &c : n.clauses) dispatch(c); } void ConstExprTraversal::visit_ifclause(const IfClause &n) { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_model(const Model &n) { for (auto &d : n.decls) dispatch(*d); for (auto &f : n.functions) dispatch(*f); for (auto &r : n.rules) dispatch(*r); } void ConstExprTraversal::visit_procedurecall(const ProcedureCall &n) { dispatch(n.call); } void ConstExprTraversal::visit_property(const Property &n) { dispatch(*n.expr); } void ConstExprTraversal::visit_propertyrule(const PropertyRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void ConstExprTraversal::visit_propertystmt(const PropertyStmt &n) { dispatch(n.property); } void ConstExprTraversal::visit_put(const Put &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstExprTraversal::visit_quantifier(const Quantifier &n) { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); } void ConstExprTraversal::visit_range(const Range &n) { dispatch(*n.min); dispatch(*n.max); } void ConstExprTraversal::visit_record(const Record &n) { for (auto &f : n.fields) dispatch(*f); } void ConstExprTraversal::visit_return(const Return &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstExprTraversal::visit_ruleset(const Ruleset &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void ConstExprTraversal::visit_scalarset(const Scalarset &n) { dispatch(*n.bound); } void ConstExprTraversal::visit_simplerule(const SimpleRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_startstate(const StartState &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_switch(const Switch &n) { dispatch(*n.expr); for (const SwitchCase &c : n.cases) dispatch(c); } void ConstExprTraversal::visit_switchcase(const SwitchCase &n) { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void ConstExprTraversal::visit_typedecl(const TypeDecl &n) { dispatch(*n.value); } void ConstExprTraversal::visit_typeexprid(const TypeExprID&) { } void ConstExprTraversal::visit_undefine(const Undefine &n) { dispatch(*n.rhs); } void ConstExprTraversal::visit_vardecl(const VarDecl &n) { if (n.type != nullptr) dispatch(*n.type); } void ConstExprTraversal::visit_while(const While &n) { dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_aliasdecl(const AliasDecl &n) { dispatch(*n.value); } void ConstStmtTraversal::visit_aliasrule(const AliasRule &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void ConstStmtTraversal::visit_add(const Add &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_and(const And &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_array(const Array &n) { dispatch(*n.index_type); dispatch(*n.element_type); } void ConstStmtTraversal::visit_bexpr(const BinaryExpr &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstStmtTraversal::visit_constdecl(const ConstDecl &n) { dispatch(*n.value); } void ConstStmtTraversal::visit_div(const Div &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_element(const Element &n) { dispatch(*n.array); dispatch(*n.index); } void ConstStmtTraversal::visit_enum(const Enum&) { } void ConstStmtTraversal::visit_eq(const Eq &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_exists(const Exists &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstStmtTraversal::visit_exprid(const ExprID&) { } void ConstStmtTraversal::visit_field(const Field &n) { dispatch(*n.record); } void ConstStmtTraversal::visit_forall(const Forall &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstStmtTraversal::visit_function(const Function &n) { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_functioncall(const FunctionCall &n) { for (auto &a : n.arguments) dispatch(*a); } void ConstStmtTraversal::visit_geq(const Geq &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_gt(const Gt &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_ifclause(const IfClause &n) { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_implication(const Implication &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_isundefined(const IsUndefined &n) { dispatch(*n.expr); } void ConstStmtTraversal::visit_leq(const Leq &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_lt(const Lt &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_mod(const Mod &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_model(const Model &n) { for (auto &d : n.decls) dispatch(*d); for (auto &f : n.functions) dispatch(*f); for (auto &r : n.rules) dispatch(*r); } void ConstStmtTraversal::visit_mul(const Mul &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_negative(const Negative &n) { visit_uexpr(n); } void ConstStmtTraversal::visit_neq(const Neq &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_not(const Not &n) { visit_uexpr(n); } void ConstStmtTraversal::visit_number(const Number&) { } void ConstStmtTraversal::visit_or(const Or &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_property(const Property &n) { dispatch(*n.expr); } void ConstStmtTraversal::visit_propertyrule(const PropertyRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void ConstStmtTraversal::visit_quantifier(const Quantifier &n) { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); } void ConstStmtTraversal::visit_range(const Range &n) { dispatch(*n.min); dispatch(*n.max); } void ConstStmtTraversal::visit_record(const Record &n) { for (auto &f : n.fields) dispatch(*f); } void ConstStmtTraversal::visit_ruleset(const Ruleset &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void ConstStmtTraversal::visit_scalarset(const Scalarset &n) { dispatch(*n.bound); } void ConstStmtTraversal::visit_simplerule(const SimpleRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_startstate(const StartState &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_switchcase(const SwitchCase &n) { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void ConstStmtTraversal::visit_sub(const Sub &n) { visit_bexpr(n); } void ConstStmtTraversal::visit_ternary(const Ternary &n) { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); } void ConstStmtTraversal::visit_typedecl(const TypeDecl &n) { dispatch(*n.value); } void ConstStmtTraversal::visit_typeexprid(const TypeExprID&) { } void ConstStmtTraversal::visit_uexpr(const UnaryExpr &n) { dispatch(*n.rhs); } void ConstStmtTraversal::visit_vardecl(const VarDecl &n) { if (n.type != nullptr) dispatch(*n.type); } void ConstTypeTraversal::visit_add(const Add &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_aliasdecl(const AliasDecl &n) { dispatch(*n.value); } void ConstTypeTraversal::visit_aliasrule(const AliasRule &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); } void ConstTypeTraversal::visit_aliasstmt(const AliasStmt &n) { for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_and(const And &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_assignment(const Assignment &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTypeTraversal::visit_bexpr(const BinaryExpr &n) { dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTypeTraversal::visit_clear(const Clear &n) { dispatch(*n.rhs); } void ConstTypeTraversal::visit_constdecl(const ConstDecl &n) { dispatch(*n.value); } void ConstTypeTraversal::visit_div(const Div &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_element(const Element &n) { dispatch(*n.array); dispatch(*n.index); } void ConstTypeTraversal::visit_eq(const Eq &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_errorstmt(const ErrorStmt&) { } void ConstTypeTraversal::visit_exists(const Exists &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstTypeTraversal::visit_exprid(const ExprID&) { } void ConstTypeTraversal::visit_field(const Field &n) { dispatch(*n.record); } void ConstTypeTraversal::visit_for(const For &n) { dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_forall(const Forall &n) { dispatch(n.quantifier); dispatch(*n.expr); } void ConstTypeTraversal::visit_function(const Function &n) { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_functioncall(const FunctionCall &n) { for (auto &a : n.arguments) dispatch(*a); } void ConstTypeTraversal::visit_geq(const Geq &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_gt(const Gt &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_if(const If &n) { for (const IfClause &c : n.clauses) dispatch(c); } void ConstTypeTraversal::visit_ifclause(const IfClause &n) { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_implication(const Implication &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_isundefined(const IsUndefined &n) { dispatch(*n.expr); } void ConstTypeTraversal::visit_leq(const Leq &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_lt(const Lt &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_mod(const Mod &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_model(const Model &n) { for (auto &d : n.decls) dispatch(*d); for (auto &f : n.functions) dispatch(*f); for (auto &r : n.rules) dispatch(*r); } void ConstTypeTraversal::visit_mul(const Mul &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_negative(const Negative &n) { visit_uexpr(n); } void ConstTypeTraversal::visit_neq(const Neq &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_not(const Not &n) { visit_uexpr(n); } void ConstTypeTraversal::visit_number(const Number&) { } void ConstTypeTraversal::visit_or(const Or &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_procedurecall(const ProcedureCall &n) { dispatch(n.call); } void ConstTypeTraversal::visit_property(const Property &n) { dispatch(*n.expr); } void ConstTypeTraversal::visit_propertyrule(const PropertyRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); } void ConstTypeTraversal::visit_propertystmt(const PropertyStmt &n) { dispatch(n.property); } void ConstTypeTraversal::visit_put(const Put &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstTypeTraversal::visit_quantifier(const Quantifier &n) { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); } void ConstTypeTraversal::visit_return(const Return &n) { if (n.expr != nullptr) dispatch(*n.expr); } void ConstTypeTraversal::visit_ruleset(const Ruleset &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); } void ConstTypeTraversal::visit_simplerule(const SimpleRule &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_startstate(const StartState &n) { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_sub(const Sub &n) { visit_bexpr(n); } void ConstTypeTraversal::visit_switch(const Switch &n) { dispatch(*n.expr); for (const SwitchCase &c : n.cases) dispatch(c); } void ConstTypeTraversal::visit_switchcase(const SwitchCase &n) { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); } void ConstTypeTraversal::visit_ternary(const Ternary &n) { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); } void ConstTypeTraversal::visit_typedecl(const TypeDecl &n) { dispatch(*n.value); } void ConstTypeTraversal::visit_uexpr(const UnaryExpr &n) { dispatch(*n.rhs); } void ConstTypeTraversal::visit_undefine(const Undefine &n) { dispatch(*n.rhs); } void ConstTypeTraversal::visit_vardecl(const VarDecl &n) { if (n.type != nullptr) dispatch(*n.type); } void ConstTypeTraversal::visit_while(const While &n) { dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); } } rumur-2020.02.17/librumur/src/utils.h000066400000000000000000000013441362265074000172270ustar00rootroot00000000000000#pragma once #include #include #include namespace rumur { // Compare two (possibly differently sized) vectors of pointers. template // __attribute__((deprecated("vector_eq will be removed in a future release"))) bool vector_eq(const std::vector &a, const std::vector &b) { // If the two vectors are of different sizes, they cannot be equal. if (a.size() != b.size()) return false; // Now we can just lean on the standard library return std::equal(a.begin(), a.end(), b.begin(), [](const T x, const T y) { return *x == *y; }); } template bool isa(const U ptr) { return ptr != nullptr && dynamic_cast(&*ptr) != nullptr; } } rumur-2020.02.17/librumur/src/validate.cc000066400000000000000000000176531362265074000200300ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rumur { namespace { class Validator : public ConstBaseTraversal { public: void visit_add(const Add &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_aliasdecl(const AliasDecl &n) final { dispatch(*n.value); n.validate(); } void visit_aliasrule(const AliasRule &n) final { for (auto &a : n.aliases) dispatch(*a); for (auto &r : n.rules) dispatch(*r); n.validate(); } void visit_aliasstmt(const AliasStmt &n) final { for (auto &a : n.aliases) dispatch(*a); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_and(const And &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_array(const Array &n) final { dispatch(*n.index_type); dispatch(*n.element_type); n.validate(); } void visit_assignment(const Assignment &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_clear(const Clear &n) final { dispatch(*n.rhs); n.validate(); } void visit_constdecl(const ConstDecl &n) final { dispatch(*n.value); n.validate(); } void visit_div(const Div &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_element(const Element &n) final { dispatch(*n.array); dispatch(*n.index); n.validate(); } void visit_enum(const Enum &n) final { n.validate(); } void visit_eq(const Eq &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_errorstmt(const ErrorStmt &n) final { n.validate(); } void visit_exists(const Exists &n) final { dispatch(n.quantifier); dispatch(*n.expr); n.validate(); } void visit_exprid(const ExprID &n) final { if (n.value != nullptr) dispatch(*n.value); n.validate(); } void visit_field(const Field &n) final { dispatch(*n.record); n.validate(); } void visit_for(const For &n) final { dispatch(n.quantifier); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_forall(const Forall &n) final { dispatch(n.quantifier); dispatch(*n.expr); n.validate(); } void visit_function(const Function &n) final { for (auto &p : n.parameters) dispatch(*p); if (n.return_type != nullptr) dispatch(*n.return_type); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_functioncall(const FunctionCall &n) final { for (auto &a : n.arguments) dispatch(*a); n.validate(); } void visit_geq(const Geq &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_gt(const Gt &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_if(const If &n) final { for (const IfClause &c : n.clauses) dispatch(c); n.validate(); } void visit_ifclause(const IfClause &n) final { if (n.condition != nullptr) dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_implication(const Implication &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_isundefined(const IsUndefined &n) final { dispatch(*n.expr); n.validate(); } void visit_leq(const Leq &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_lt(const Lt &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_mod(const Mod &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_model(const Model &n) final { for (auto &d : n.decls) dispatch(*d); for (auto &f : n.functions) dispatch(*f); for (auto &r : n.rules) dispatch(*r); n.validate(); } void visit_mul(const Mul &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_negative(const Negative &n) final { dispatch(*n.rhs); n.validate(); } void visit_neq(const Neq &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_not(const Not &n) final { dispatch(*n.rhs); n.validate(); } void visit_number(const Number &n) final { n.validate(); } void visit_or(const Or &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_procedurecall(const ProcedureCall &n) final { dispatch(n.call); n.validate(); } void visit_property(const Property &n) final { dispatch(*n.expr); n.validate(); } void visit_propertyrule(const PropertyRule &n) final { for (const Quantifier &q : n.quantifiers) dispatch(q); dispatch(n.property); n.validate(); } void visit_propertystmt(const PropertyStmt &n) final { dispatch(n.property); n.validate(); } void visit_put(const Put &n) final { if (n.expr != nullptr) dispatch(*n.expr); n.validate(); } void visit_quantifier(const Quantifier &n) final { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); n.validate(); } void visit_range(const Range &n) final { dispatch(*n.min); dispatch(*n.max); n.validate(); } void visit_record(const Record &n) final { for (auto &f : n.fields) dispatch(*f); n.validate(); } void visit_return(const Return &n) final { if (n.expr != nullptr) dispatch(*n.expr); n.validate(); } void visit_ruleset(const Ruleset &n) final { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &r : n.rules) dispatch(*r); n.validate(); } void visit_scalarset(const Scalarset &n) final { dispatch(*n.bound); n.validate(); } void visit_simplerule(const SimpleRule &n) final { for (const Quantifier &q : n.quantifiers) dispatch(q); if (n.guard != nullptr) dispatch(*n.guard); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_startstate(const StartState &n) final { for (const Quantifier &q : n.quantifiers) dispatch(q); for (auto &d : n.decls) dispatch(*d); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_sub(const Sub &n) final { dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_switch(const Switch &n) final { dispatch(*n.expr); for (const SwitchCase &c : n.cases) dispatch(c); n.validate(); } void visit_switchcase(const SwitchCase &n) final { for (auto &m : n.matches) dispatch(*m); for (auto &s : n.body) dispatch(*s); n.validate(); } void visit_ternary(const Ternary &n) final { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); n.validate(); } void visit_typedecl(const TypeDecl &n) final { dispatch(*n.value); n.validate(); } void visit_typeexprid(const TypeExprID &n) final { if (n.referent != nullptr) dispatch(*n.referent); n.validate(); } void visit_undefine(const Undefine &n) final { dispatch(*n.rhs); n.validate(); } void visit_vardecl(const VarDecl &n) final { if (n.type != nullptr) dispatch(*n.type); n.validate(); } void visit_while(const While &n) final { dispatch(*n.condition); for (auto &s : n.body) dispatch(*s); n.validate(); } virtual ~Validator() = default; }; } void validate(const Node &n) { Validator v; v.dispatch(n); } } rumur-2020.02.17/misc/000077500000000000000000000000001362265074000142175ustar00rootroot00000000000000rumur-2020.02.17/misc/_rumur000066400000000000000000000040571362265074000154610ustar00rootroot00000000000000#compdef rumur # Zsh completion script for Rumur _arguments \ '--bound[limit of the state space exploration depth]:steps' \ '--colour[enable or disable ANSI colour codes]: :(auto off on)' \ '--counterexample-trace[how to print counterexample traces]: :(diff full off)' \ '--deadlock-detection[deadlock semantics to use]: :(off stuck stuttering)' \ {--debug,-d}'[enabled debugging mode]' \ '--help[display help information]' \ '--max-errors[number of errors to report before exiting]:count' \ '--monopolise[use all machine resources]' \ {--output,-o}'[path to write C verifier to]:filename:_files' \ '--output-format[how verifier should print output]: :(machine-readable human-readable)' \ '--pack-state[compress verifier auxiliary state]: :(on off)' \ {--quiet,-q}'[suppress output while generating verifier]' \ '--sandbox[verifier privilege restriction]: :(on off)' \ {--set-capacity,-s}'[initial memory (in bytes) to allocate for the seen set]:SIZE' \ {--set-expand-threshold,-e}'[limit at which to expand the seen set]:occupancy percentage' \ '--smt-arg[argument to pass to SMT solver]:ARG' \ '--smt-bitvectors[disable or enable using bitvectors instead of unbounded integers in SMT translation]: :(off on)' \ '--smt-budget[time allotment for SMT solver]:MILLISECONDS' \ '--smt-path[path to SMT solver]:path:_cmdstring' \ '--smt-prelude[text to pass to SMT solver preceding problems]:TEXT' \ '--smt-simplification[disable or enable using SMT solver for simplification]: :(off on)' \ '--symmetry-reduction[symmetry reduction optimisation]: :(off heuristic exhaustive)' \ {--threads,-t}'[number of threads to use in the verifier]:count' \ '--trace[tracing messages to print in the verifier]: :(handle_reads handle_writes queue set symmetry_reduction all)' \ '--value-type[C type to use for scalar values in the verifier]: :(auto int8_t uint8_t int16_t uint16_t int32_t uint32_t int64_t uint64_t)' \ {--verbose,-v}'[output more detail while generating verifier]' \ '--version[output version information]' \ '*::filename:_files -g "*.m"' rumur-2020.02.17/misc/afl-wrapper.sh000077500000000000000000000011701362265074000167750ustar00rootroot00000000000000#!/usr/bin/env bash # wrapper around American Fuzzy Lop for use in CI (see ../.travis.yml) # echo commands set -x # run AFL fuzzing for 30m timeout --preserve-status 1800s afl-fuzz -m 8192 -i ../tests -o findings_dir -- rumur --output /tmp/model.c @@ # remove the crashes dir if it is empty; this will fail if not empty rmdir findings_dir/crashes &>/dev/null # if we did not find any crashes, we are done if [ ! -e findings_dir/crashes ]; then printf 'no crashes\n' exit 0 fi # display information about any crashing models find findings_dir/crashes -type f -exec printf 'crash in %s:\n' "{}" \; -exec cat "{}" \; exit 1 rumur-2020.02.17/misc/install-afl.sh000077500000000000000000000010361362265074000167640ustar00rootroot00000000000000#!/usr/bin/env bash # Download and install AFL. For use in CI. if [ -z "${CI}" ]; then printf 'CI variable not set. Set it and re-run if you really meant this to execute.\n' >&2 exit 1 fi # exit on error set -e # echo all commands set -x # create a temporary space to work in TMP=$(mktemp -d) pushd "${TMP}" # download curl --retry 3 -O http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz # decompress tar xf afl-latest.tgz rm afl-latest.tgz cd afl* # compile make # install sudo make install # clean up popd rm -rf "${TMP}" rumur-2020.02.17/misc/install-macports.sh000077500000000000000000000010761362265074000200560ustar00rootroot00000000000000#!/usr/bin/env bash # Download and install Macports. For use in CI. # Version of Macports to install VERSION=2.6.2 if [ "$(uname)" != "Darwin" ]; then printf 'this script is only intended to run on macOS\n' exit 1 fi # Exit on error set -e # Echo all commands set -x # Create a temporary space to work in TMP=$(mktemp -d) cd "${TMP}" # Download curl --retry 3 -O https://distfiles.macports.org/MacPorts/MacPorts-${VERSION}.tar.bz2 # Decompress tar xf MacPorts-${VERSION}.tar.bz2 cd MacPorts-${VERSION} # Configure and install ./configure make sudo make install rumur-2020.02.17/misc/murphi-mode.el000066400000000000000000000026051362265074000167720ustar00rootroot00000000000000;;; murphi-mode.el ;; major mode for editing Murphi source files ;; Author: Matthew Fernandez ;; Homepage: https://github.com/smattr/rumur (setq murphi-highlights '( ("--.*\n" . font-lock-comment-delimiter-face) ; FIXME: how do we match multiline comments? ("\\_<\\(alias\\|assert\\|begin\\|by\\|case\\|clear\\|const\\|do\\|else\\|elsif\\|end\\|endalias\\|endexists\\|endfor\\|endforall\\|endfunction\\|endif\\|endprocedure\\|endrecord\\|endrule\\|endruleset\\|endstartstate\\|endswitch\\|endwhile\\|error\\|exists\\|for\\|forall\\|if\\|in\\|isundefined\\|of\\|put\\|return\\|switch\\|then\\|to\\|type\\|undefine\\|var\\|while\\)\\_>" . font-lock-keyword-face) ("\\_<\\(false\\|true\\)\\_>" . font-lock-constant-face) ("\\_<-*[0-9]+\\_>" . font-lock-constant-face) ("\\_<\\(function\\|procedure\\|invariant\\|rule\\|ruleset\\|startstate\\)\\_>" . font-lock-function-name-face) ; FIXME: why does the following take precedence over a -- comment? ; TODO: support for \" as an escape within strings ("\"[^\"]*\"" . font-lock-string-face) ("\\_<\\(array\\|boolean\\|enum\\|record\\|scalarset\\|union\\)\\_>" . font-lock-type-face) )) (define-derived-mode murphi-mode fundamental-mode "murphi" "major mode for editing Murphi source code" (setq font-lock-defaults '(murphi-highlights)) (setq font-lock-keywords-case-fold-search t) ) rumur-2020.02.17/misc/murphi.vim000066400000000000000000000021201362265074000162330ustar00rootroot00000000000000" Vim support for Rumur extensions to the Murphi syntax " Language: murphi " Maintainer: Matthew Fernandez " Lincese: The Unlicense " Vim already knows how to highlight Murphi files, but doesn't know about the " extra things that Rumur supports. To use this file, copy it into " ~/.vim/after/syntax/. The Rumur extensions to Murphi should now be correctly " highlighted when editing models. " extra keywords that Rumur supports syntax case ignore syn keyword murphiKeyword assume syn keyword murphiKeyword cover syn keyword murphiKeyword liveness syntax case match " support for hex and octal numbers in addition to decimal syn match murphiNumber "\<\(0[xX]\x\+\|0\o+\|[1-9]\d*\)\>" " error-highlight bad octals syn match murphiError "\<0\o*[89]" " override the base syntax's highlighting of `==` as an error syn match murphiOperator "==[^>]"he=e-1 " UTF-8 operators that Rumur recognises syn match murphiOperator "[∀∃≔≥→≤≠⇒¬∧∨]" " recognise escape sequences in strings syn region murphiString start=+"\|“+ skip=+\\\\\|\\"\|\\”+ end=+"\|”+ rumur-2020.02.17/misc/murphi2xml.rng000066400000000000000000000623021362265074000170410ustar00rootroot00000000000000 [a-zA-Z_][a-zA-Z0-9_]* (assertion|assumption|cover|liveness) [01] rumur-2020.02.17/misc/package-for-debian.sh000077500000000000000000000040561362265074000201620ustar00rootroot00000000000000#!/usr/bin/env bash # Package Rumur for Debian. To use this, you must be on the 'packaging/debian' # branch. # Some relevant notes for Debian packaging: # * Pass --date=rfc2822 to git-log to get the commit dates in the right format # for Debian packaging. # * After successful packaging, sign the release with # `debsign ../rumur__source.changes`. # * Upload the package to mentors.debian.net with # `dput mentors ../rumur__source.changes`. # * Check consistency of ../debian/watch with # `cd .. && uscan --report --verbose`. # * For building with pbuilder, first # `sudo pbuilder create --debootstrapopts --variant=buildd` to setup a base # chroot image, then `pdebuild` to test. # * To update a pre-existing pbuilder environment, `sudo pbuilder update`. # # For uploading to mentors.debian.net, you will need ~/.dput.cf configured: # # [mentors] # fqdn = mentors.debian.net # incoming = /upload # method = https # allow_unsigned_uploads = 0 # progress_indicator = 2 # allowed_distributions = .* if [ "$(uname -s)" != "Linux" ]; then printf 'This script is only intended to run on Linux (Debian unstable)\n' >&2 exit 1 fi if [ "$(lsb_release --id | cut -d " " -f2)" != "Debian" ]; then printf 'This script is only intended to run on Debian unstable\n' >&2 exit 1 fi # Move to our parent directory cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)/.." # check for artefacts from a prior build ls ../rumur_* &>/dev/null if [ $? -eq 0 ]; then printf 'There are files leftover from the previous build (../rumur_*)\n' >&2 exit 1 fi # check git tree if [[ ! -z "$(git status --short)" ]]; then printf 'Working tree is not clean\n' >&2 exit 1 fi # Exit on error set -e # Echo all commands set -x # Build the package from scratch gbp buildpackage --git-ignore-new --git-upstream-tag='v%(version)s' --git-debian-branch=packaging/debian # Check for consistency errors in the built package lintian -i -I --show-overrides printf 'Run pdebuild to test the package under pbuilder\n' rumur-2020.02.17/misc/pending-queue-4k.m000066400000000000000000000626601362265074000174710ustar00rootroot00000000000000/* Model of the pending queue algorithm in the generated verifier. This was * originally adapted from pending-queue.m. * * The generated verifier uses a moderately complex lock-free algorithm for * enqueueing and dequeueing to the per-thread pending states queue. The * argument for the correctness of this algorithm is non-trivial and quite * subtle in places. The model below attempts to capture the abstract logic of * this algorithm, with the intent that we can gain some more evidence for its * correctness. * * Where we abstract the implementation: * * In the implementation, each thread maintains the ID of the queue it last * dequeued from (`queue_id`), which is then the first queue it tries to * dequeue from next time. This is an optimisation to reduce scanning * likely-empty queues again. This ID is reset to the thread's own queue * when it enqueues there to terminate work-stealing and switch back to * normal operation. In the model below, we remove this constraint and let * any thread try to dequeue from any queue. This is a safe * over-approximation. * * In the implementation, threads only ever enqueue to their own queue. In * the model we let threads enqueue to arbitrary queues. This is a safe * over-approximation and represents something we might want to consider in * future. * * The queue nodes of the implementation store * `4096 / sizeof(struct state*) - 1` states. In the model, we store fewer * (`STATES_PER_NODE`) to make the model manageable. */ const -- number of threads THREADS: 2 /* Number of queues. We use per-thread queues, so this is always the same as * the number of threads. */ QUEUES: THREADS /* Number of queue nodes available for allocation. Indirectly, this would be * something like the available heap memory. */ QUEUE_NODES: 2 /* Number of states stored per queue node. In the implementation, this is * `4096 / sizeof(struct state*) - 1`, but we abstract this here to a value * that should still be reasonable enough to expose any concurrency problems. */ STATES_PER_NODE: 2 -- a value indicating an invalid queue node NULL: -1 type -- thread identifier thread_id_t: 0 .. THREADS - 1 -- Queue identifier. Same as thread identifier as we have per-thread queues. queue_id_t: 0 .. QUEUES - 1 /* Queue node identifier. This is an abstraction of what would be virtual * address in the real system. */ queue_node_id: 0 .. QUEUE_NODES - 1 /* As above, but with `-1` to indicate an invalid queue node. Similar to the * way we use `NULL` in the real system. */ queue_node_id_opt: -1 .. QUEUE_NODES - 1 -- handle to either a state within a queue node or the node's next pointer queue_handle_t: record node: queue_node_id_opt offset: 0 .. STATES_PER_NODE end -- the contents of a queue node itself queue_node: record /* we only model whether a state slot is occupied or not, not what actual * state pointer it contains */ s: array[0 .. STATES_PER_NODE - 1] of boolean next: queue_node_id_opt end -- a per-thread queue queue: record head: queue_handle_t -- handle to the start of the queue tail: queue_handle_t -- handle to the end of the queue end /* A two-pointer structure. This is more general in the implementation, but in * the context of this model we only ever need to talk about a struct of two * queue handles, so we make this definition specific. */ double_ptr_t: record head: queue_handle_t tail: queue_handle_t end -- local state used during enqueue() enqueue_state: record ends: double_ptr_t ends_check: double_ptr_t n: queue_node_id_opt new: double_ptr_t queue_id: queue_id_t new_node: queue_node_id_opt next_tail: queue_handle_t end -- local state used during dequeue() dequeue_state: record attempts: 0 .. THREADS queue_id: queue_id_t ends: double_ptr_t ends_check: double_ptr_t new: double_ptr_t new_head: queue_node_id_opt reclaim_call: enum { L1911, L1940 } old: double_ptr_t s: boolean end -- local state used during reclaim() reclaim_state: record i: 0 .. THREADS j: 0 .. THREADS conflict: boolean end /* Program counter values. Note that some of these suffixed with 'L*' refer to * source code lines in ../rumur/resources/header.c at the time of writing. * These may have drifted out of sync with changes since then, so if you want * to cross reference these you may have to consult the Git commit in which * this model was added. */ label_t: enum { -- not running any operation IDLE, -- running enqueue() ENQUEUE_L1708, ENQUEUE_L1723, ENQUEUE_L1726, ENQUEUE_L1737, ENQUEUE_L1740, ENQUEUE_L1751, ENQUEUE_L1764, ENQUEUE_L1784, ENQUEUE_L1787, ENQUEUE_L1788, ENQUEUE_L1797, ENQUEUE_L1813, ENQUEUE_L1829, ENQUEUE_L1832, ENQUEUE_L1840, -- running dequeue() DEQUEUE_L1859, DEQUEUE_L1861, DEQUEUE_L1870, DEQUEUE_L1873, DEQUEUE_L1882, DEQUEUE_L1904, DEQUEUE_L1908, DEQUEUE_L1909, DEQUEUE_L1913, DEQUEUE_L1919, DEQUEUE_L1922, DEQUEUE_L1932, DEQUEUE_L1939, DEQUEUE_L1943, -- running reclaim() RECLAIM_L1503, RECLAIM_BLOCK_2, RECLAIM_BLOCK_3, RECLAIM_BLOCK_4, RECLAIM_BLOCK_5 } -- thread-local state thread_local: record pc: label_t -- program counter head: queue_node_id_opt -- queue head being currently examined tail: queue_node_id_opt -- queue tail being currently examined enqueue_locals: enqueue_state -- state used during enqueue() dequeue_locals: dequeue_state -- state used during dequeue() reclaim_locals: reclaim_state -- state used during reclaim() deferred: array [0 .. THREADS - 1] of queue_node_id_opt -- deferred pointers to free used in reclaim() end var /* Globally addressable queue nodes. You can think of this as memory indexed * by virtual address. */ queue_nodes: array [queue_node_id] of queue_node -- IDs of free queue nodes from which malloc allocates freelist: array [queue_node_id] of boolean -- the pending queues for each thread q: array [queue_id_t] of queue -- thread-local states thread_locals: array [thread_id_t] of thread_local -- hazarded pointers hazarded: array [thread_id_t] of queue_node_id_opt function queue_node_new(): queue_node_id_opt; begin -- try to find a free queue node /* XXX: awkward hack here where we need to loop over queue_node_id_opt instead * of queue_node_id because Murphi doesn't like us returning a value of a * different type than the function's return type. */ for i: queue_node_id_opt do if i != NULL then if freelist[i] then freelist[i] := false; -- memset 0 for j: 0 .. STATES_PER_NODE - 1 do queue_nodes[i].s[j] := false; end; queue_nodes[i].next := NULL; return i; end; end; end; -- all queue nodes are in-use (allocated) return NULL; end procedure queue_node_free(p: queue_node_id_opt); begin -- allow callers to free NULL as a no-op if p = NULL then return; end; assert !freelist[p] "freeing a queue node that was not in use"; freelist[p] := true; end function queue_handle_from_node_ptr(n: queue_node_id_opt): queue_handle_t; var tmp: queue_handle_t begin tmp.node := n; tmp.offset := 0; return tmp; end function queue_handle_base(h: queue_handle_t): queue_node_id_opt; return h.node; end function queue_handle_is_state_pptr(h: queue_handle_t): boolean; begin return h.offset < STATES_PER_NODE; end function queue_handle_next(h: queue_handle_t): queue_handle_t; var tmp: queue_handle_t begin assert h.offset < STATES_PER_NODE "queue_handle_next() called on exhausted queue handle"; tmp.node := h.node; tmp.offset := h.offset + 1; return tmp; end -- is the given queue node in the given queue? function in_queue(qid: queue_id_t; qnid: queue_node_id): boolean; var i: queue_node_id_opt begin -- follow the linked-list of queue nodes, looking for our target i := q[qid].head.node; while i != NULL do if i = qnid then return true; end; i := queue_nodes[i].next; end; -- we didn't find it return false; end function double_ptr_make(head: queue_handle_t; tail: queue_handle_t): double_ptr_t; var tmp: double_ptr_t begin tmp.head := head; tmp.tail := tail; return tmp; end procedure hazard(thread_id: thread_id_t; h: queue_handle_t); var p: queue_node_id_opt begin p := queue_handle_base(h); assert hazarded[thread_id] = NULL "hazarding multiple pointers at once"; hazarded[thread_id] := p; end procedure unhazard(thread_id: thread_id_t; h: queue_handle_t); var p: queue_node_id_opt begin p := queue_handle_base(h); assert hazarded[thread_id] != NULL "unhazarding a pointer when none are hazarded"; assert hazarded[thread_id] = p "unhazarding a pointer that differs from the one hazarded"; hazarded[thread_id] := NULL; end procedure return_from_reclaim(thread_id: thread_id_t); begin alias reclaim_call: thread_locals[thread_id].dequeue_locals.reclaim_call pc: thread_locals[thread_id].pc do assert !isundefined(reclaim_call) "unknown reclaim() call label"; if reclaim_call = L1911 then pc := DEQUEUE_L1913; else pc := DEQUEUE_L1943 end; undefine reclaim_call; end; end procedure goto_idle(thread_id: thread_id_t); begin -- blank all function-local state undefine thread_locals[thread_id].enqueue_locals; undefine thread_locals[thread_id].dequeue_locals; undefine thread_locals[thread_id].reclaim_locals; thread_locals[thread_id].pc := IDLE; end startstate begin -- mark all queue nodes as unallocated for qnid: queue_node_id do freelist[qnid] := true; end; -- set all queues as empty for qid: queue_id_t do q[qid].head.node := NULL; q[qid].head.offset := 0; q[qid].tail.node := NULL; q[qid].tail.offset := 0; end; -- reset all threads for thread_id: thread_id_t do goto_idle(thread_id); end; -- we start with no deferred deallocations for thread_id: thread_id_t do for i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] := NULL; end; end; -- we start with no hazarded pointers for i: thread_id_t do hazarded[i] := NULL; end; end ruleset thread_id: thread_id_t do alias pc: thread_locals[thread_id].pc do ruleset queue_id: queue_id_t do rule "enqueue start" pc = IDLE ==> begin thread_locals[thread_id].enqueue_locals.queue_id := queue_id; thread_locals[thread_id].enqueue_locals.ends := q[queue_id]; pc := ENQUEUE_L1708; end end alias ends: thread_locals[thread_id].enqueue_locals.ends ends_check: thread_locals[thread_id].enqueue_locals.ends_check head: thread_locals[thread_id].enqueue_locals.ends.head n: thread_locals[thread_id].enqueue_locals.n new: thread_locals[thread_id].enqueue_locals.new queue_id: thread_locals[thread_id].enqueue_locals.queue_id tail: thread_locals[thread_id].enqueue_locals.ends.tail new_node: thread_locals[thread_id].enqueue_locals.new_node next_tail: thread_locals[thread_id].enqueue_locals.next_tail do rule "header.c:1708" pc = ENQUEUE_L1708 ==> begin if tail.node = NULL then assert tail.offset = 0 "queue_node_offset has non-zero offset with null node"; assert head.node = NULL "tail of queue 0 while head is non-0"; assert head.offset = 0 "queue_node_offset has non-zero offset with null node"; n := queue_node_new(); assume n != NULL; -- avoid dealing with OOM queue_nodes[n].s[0] := true; new := double_ptr_make(queue_handle_from_node_ptr(n), queue_handle_from_node_ptr(n)); pc := ENQUEUE_L1723; else hazard(thread_id, tail); pc := ENQUEUE_L1737; end; end rule "header.c:1723" pc = ENQUEUE_L1723 ==> begin if q[queue_id] = ends then q[queue_id] := new; goto_idle(thread_id); else ends := q[queue_id]; pc := ENQUEUE_L1726; end; end rule "header.c:1726" pc = ENQUEUE_L1726 ==> begin queue_node_free(n); undefine n; pc := ENQUEUE_L1708; -- "goto retry" end rule "header.c:1737" pc = ENQUEUE_L1737 ==> begin ends_check := q[queue_id]; if ends != ends_check then pc := ENQUEUE_L1740; else pc := ENQUEUE_L1751; end; end rule "header.c:1740" pc = ENQUEUE_L1740 ==> begin unhazard(thread_id, tail); ends := ends_check; undefine ends_check; pc := ENQUEUE_L1708; -- "goto retry" end rule "header.c:1751" pc = ENQUEUE_L1751 ==> begin next_tail := queue_handle_next(tail); if queue_handle_is_state_pptr(next_tail) then alias target: queue_nodes[next_tail.node].s[next_tail.offset] do if !target then target := true; pc := ENQUEUE_L1797; else undefine next_tail; pc := ENQUEUE_L1764; end; end; else new_node := queue_node_new(); assume new_node != NULL; -- avoid dealing with OOM queue_nodes[new_node].s[0] := true; pc := ENQUEUE_L1784; end; end rule "header.c:1764" pc = ENQUEUE_L1764 ==> begin unhazard(thread_id, tail); pc := ENQUEUE_L1708; -- "goto retry" end rule "header.c:1784" pc = ENQUEUE_L1784 ==> begin alias target: queue_nodes[next_tail.node].next do if target = NULL then target := new_node; next_tail := queue_handle_from_node_ptr(new_node); pc := ENQUEUE_L1797; else undefine next_tail; pc := ENQUEUE_L1787; end; end; end rule "header.c:1787" pc = ENQUEUE_L1787 ==> begin queue_node_free(new_node); undefine new_node; pc := ENQUEUE_L1788; end rule "header.c:1788" pc = ENQUEUE_L1788 ==> begin unhazard(thread_id, tail); pc := ENQUEUE_L1708; -- "goto retry" end rule "header.c:1797" pc = ENQUEUE_L1797 ==> begin new := double_ptr_make(head, next_tail); if q[queue_id] = ends then q[queue_id] := new; pc := ENQUEUE_L1840; else -- use `ends_check` instead of `old` to save a state variable ends_check := q[queue_id]; pc := ENQUEUE_L1813; end; end rule "header.c:1813" pc = ENQUEUE_L1813 ==> begin next_tail := queue_handle_next(tail); if queue_handle_is_state_pptr(next_tail) then alias target: queue_nodes[next_tail.node].s[next_tail.offset] do if target then target := false; pc := ENQUEUE_L1832; else assert false "undo of write to next_tail failed"; end; end; else alias target: queue_nodes[next_tail.node].next do if target = new_node then target := NULL; pc := ENQUEUE_L1829; else assert false "undo of write to next_tail failed"; end; end; end; end rule "header.c:1829" pc = ENQUEUE_L1829 ==> begin queue_node_free(new_node); undefine new_node; pc := ENQUEUE_L1832; end rule "header.c:1832" pc = ENQUEUE_L1832 ==> begin unhazard(thread_id, tail); ends := ends_check; undefine ends_check; pc := ENQUEUE_L1708; -- "goto retry" end rule "header.c:1840" pc = ENQUEUE_L1840 ==> begin unhazard(thread_id, tail); goto_idle(thread_id); end end ruleset queue_id: queue_id_t do rule "dequeue start" thread_locals[thread_id].pc = IDLE ==> begin thread_locals[thread_id].dequeue_locals.attempts := 0; thread_locals[thread_id].dequeue_locals.queue_id := queue_id; thread_locals[thread_id].dequeue_locals.ends := q[queue_id]; thread_locals[thread_id].pc := DEQUEUE_L1861; end end alias ends: thread_locals[thread_id].dequeue_locals.ends head: thread_locals[thread_id].dequeue_locals.ends.head tail: thread_locals[thread_id].dequeue_locals.ends.tail queue_id: thread_locals[thread_id].dequeue_locals.queue_id attempts: thread_locals[thread_id].dequeue_locals.attempts ends_check: thread_locals[thread_id].dequeue_locals.ends_check new: thread_locals[thread_id].dequeue_locals.new new_head: thread_locals[thread_id].dequeue_locals.new_head reclaim_call: thread_locals[thread_id].dequeue_locals.reclaim_call old: thread_locals[thread_id].dequeue_locals.old s: thread_locals[thread_id].dequeue_locals.s do rule "header.c:1859" pc = DEQUEUE_L1859 ==> begin ends := q[queue_id]; pc := DEQUEUE_L1861; end rule "header.c:1861" pc = DEQUEUE_L1861 ==> begin if head.node != NULL then hazard(thread_id, head); pc := DEQUEUE_L1870; else queue_id := (queue_id + 1) % QUEUES; attempts := attempts + 1; if attempts < QUEUES then pc := DEQUEUE_L1859; else goto_idle(thread_id); end; end; end rule "header.c:1870" pc = DEQUEUE_L1870 ==> begin ends_check := q[queue_id]; if ends != ends_check then pc := DEQUEUE_L1873; else undefine ends_check; pc := DEQUEUE_L1882; end; end rule "header.c:1873" pc = DEQUEUE_L1873 ==> begin unhazard(thread_id, head); ends := ends_check; undefine ends_check; pc := DEQUEUE_L1861; -- "goto retry" end rule "header.c:1882" pc = DEQUEUE_L1882 ==> var zero: queue_handle_t begin if head = tail then zero.node := NULL; zero.offset := 0; new := double_ptr_make(zero, zero); pc := DEQUEUE_L1919; elsif queue_handle_is_state_pptr(head) then new := double_ptr_make(queue_handle_next(head), tail); pc := DEQUEUE_L1919; else new_head := queue_nodes[head.node].next; new := double_ptr_make(queue_handle_from_node_ptr(new_head), tail); pc := DEQUEUE_L1904; end; end rule "header.c:1904" pc = DEQUEUE_L1904 ==> begin old := q[queue_id]; if q[queue_id] = ends then q[queue_id] := new; end; pc := DEQUEUE_L1908; end rule "header.c:1908" pc = DEQUEUE_L1908 ==> begin unhazard(thread_id, head); pc := DEQUEUE_L1909; end rule "header.c:1909" pc = DEQUEUE_L1909 ==> begin if old = ends then reclaim_call := L1911; pc := RECLAIM_L1503; else pc := DEQUEUE_L1913; end; end rule "header.c:1913" pc = DEQUEUE_L1913 ==> begin ends := old; undefine old; pc := DEQUEUE_L1861; -- "goto retry" end rule "header.c:1919" pc = DEQUEUE_L1919 ==> begin if q[queue_id] = ends then q[queue_id] := new; undefine new; pc := DEQUEUE_L1932; else old := q[queue_id]; undefine new; pc := DEQUEUE_L1922; end; end rule "header.c:1922" pc = DEQUEUE_L1922 ==> begin unhazard(thread_id, head); ends := old; undefine old; pc := DEQUEUE_L1861; -- "goto retry" end rule "header.c:1932" pc = DEQUEUE_L1932 ==> begin if queue_handle_is_state_pptr(head) then s := true; else s := false; end; unhazard(thread_id, head); pc := DEQUEUE_L1939; end; rule "header.c:1939" pc = DEQUEUE_L1939 ==> begin if head = tail | !queue_handle_is_state_pptr(head) then reclaim_call := L1940; pc := RECLAIM_L1503; else pc := DEQUEUE_L1943; end; end rule "header.c:1943" pc = DEQUEUE_L1943 ==> begin if !s then queue_id := (queue_id + 1) % QUEUES; attempts := attempts + 1; if attempts < QUEUES then pc := DEQUEUE_L1859; else goto_idle(thread_id); end; else goto_idle(thread_id); end; end end alias head: thread_locals[thread_id].dequeue_locals.ends.head reclaim_call: thread_locals[thread_id].dequeue_locals.reclaim_call i: thread_locals[thread_id].reclaim_locals.i j: thread_locals[thread_id].reclaim_locals.j conflict: thread_locals[thread_id].reclaim_locals.conflict deferred: thread_locals[thread_id].deferred do rule "header.c:1503" pc = RECLAIM_L1503 ==> begin assert !isundefined(reclaim_call) "no return address when entering reclaim()"; assert head.node != NULL "reclaiming a null pointer"; assert hazarded[thread_id] = NULL "reclaiming a pointer while holding a hazarded pointer"; -- compressed loop iterations until we find something interesting i := 0; while i < THREADS & deferred[i] = NULL do i := i + 1; end; if i < THREADS then conflict := false; j := 0; pc := RECLAIM_BLOCK_2; else conflict := false; i := 0; pc := RECLAIM_BLOCK_4; end; end rule "reclaim block 2" pc = RECLAIM_BLOCK_2 ==> begin assert i < THREADS; assert deferred[i] != NULL; if j = thread_id then assert hazarded[j] = NULL; elsif deferred[i] = hazarded[j] then conflict := true; j := THREADS - 1; -- "break" end; j := j + 1; if j = THREADS then undefine j; pc := RECLAIM_BLOCK_3; end; end rule "reclaim block 3" pc = RECLAIM_BLOCK_3 ==> begin if !conflict then queue_node_free(deferred[i]); deferred[i] := NULL; end; undefine conflict; while i < THREADS & deferred[i] = NULL do i := i + 1; end; if i < THREADS then conflict := false; j := 0; pc := RECLAIM_BLOCK_2; else conflict := false; i := 0; pc := RECLAIM_BLOCK_4; end; end rule "reclaim block 4" pc = RECLAIM_BLOCK_4 ==> begin assert i < THREADS; if i = thread_id then assert hazarded[i] = NULL; elsif head.node = hazarded[i] then conflict := true; i := THREADS - 1; -- "break" end; i := i + 1; if i = THREADS then undefine i; pc := RECLAIM_BLOCK_5; end; end rule "reclaim block 5" pc = RECLAIM_BLOCK_5 ==> begin if !conflict then queue_node_free(head.node); return_from_reclaim(thread_id); return; else for ii: 0 .. THREADS - 1 do if deferred[ii] = NULL then deferred[ii] := head.node; return_from_reclaim(thread_id); return; end; end; end; assert false "deferred more than `THREADS` reclamations"; end end end end invariant "queue only empty when head and tail agree" forall qid: queue_id_t do (q[qid].head.node = NULL -> q[qid].head.offset = 0) & (q[qid].head.node = NULL -> q[qid].tail.node = NULL) & (q[qid].tail.node = NULL -> q[qid].tail.offset = 0) & (q[qid].tail.node = NULL -> q[qid].head.node = NULL) end invariant "no memory leaks" /* at quiescence, every queue node is either in a queue, in the free list, or * in a thread's deferred deallocations list */ exists thread_id: thread_id_t do thread_locals[thread_id].pc != IDLE end | forall qnid: queue_node_id do exists qid: queue_id_t do in_queue(qid, qnid) end | freelist[qnid] | exists thread_id: thread_id_t do exists i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] = qnid end end end invariant "no double use of nodes" -- every queue node in a queue is nowhere else forall qnid: queue_node_id do forall qid: queue_id_t do in_queue(qid, qnid) -> (forall qid2: queue_id_t do qid2 = qid | !in_queue(qid2, qnid) end & !freelist[qnid] & forall thread_id: thread_id_t do forall i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] != qnid end end) end end rumur-2020.02.17/misc/pending-queue.m000066400000000000000000000440201362265074000171430ustar00rootroot00000000000000/* Update: the following is based on a prior pending queue algorithm which used * a single-state-per-node linked-list. It has been retained as an interesting * large model to run through Rumur. * * ---- * * Model of the pending queue algorithm in the generated verifier. * * The generated verifier uses a moderately complex lock-free algorithm for * enqueueing and dequeueing to the per-thread pending states queue. The * argument for the correctness of this algorithm is non-trivial and quite * subtle in places. The model below attempts to capture the abstract logic of * this algorithm, with the intent that we can gain some more evidence for its * correctness. * * Where we abstract the implementation: * * In the implementation, each thread maintains the ID of the queue it last * dequeued from (`queue_id`), which is then the first queue it tries to * dequeue from next time. This is an optimisation to reduce scanning * likely-empty queues again. This ID is reset to the thread's own queue * when it enqueues there to terminate work-stealing and switch back to * normal operation. In the model below, we remove this constraint and let * any thread try to dequeue from any queue. This is a safe * over-approximation. * * In the implementation, threads only ever enqueue to their own queue. In * the model we let threads enqueue to arbitrary queues. This is a safe * over-approximation and represents something we might want to consider in * future. */ const -- number of threads THREADS: 2 /* Number of queues. We use per-thread queues, so this is always the same as * the number of threads. */ QUEUES: THREADS /* Number of queue nodes available for allocation. Indirectly, this would be * something like the available heap memory. */ QUEUE_NODES: 4 -- a value indicating an invalid queue node NULL: -1 type -- thread identifier thread_id_t: 0 .. THREADS - 1 -- Queue identifier. Same as thread identifier as we have per-thread queues. queue_id_t: 0 .. QUEUES - 1 /* Queue node identifier. This is an abstraction of what would be virtual * address in the real system. */ queue_node_id: 0 .. QUEUE_NODES - 1 /* As above, but with `-1` to indicate an invalid queue node. Similar to the * way we use `NULL` in the real system. */ queue_node_id_opt: -1 .. QUEUE_NODES - 1 -- the contents of a queue node itself queue_node: record -- we ignored the state member that is not relevant for verification next: queue_node_id_opt end -- a per-thread queue queue: record head: queue_node_id_opt -- pointer to the start of the queue tail: queue_node_id_opt -- pointer to the end of the queue end /* A two-pointer structure. This is more general in the implementation, but in * the context of this model we only ever need to talk about a struct of two * queue pointers, so we make this definition specific. */ double_ptr_t: record head: queue_node_id_opt tail: queue_node_id_opt end -- local state used during enqueue() enqueue_state: record ends: double_ptr_t ends_check: double_ptr_t n: queue_node_id_opt new: double_ptr_t queue_id: queue_id_t end -- local state used during dequeue() dequeue_state: record attempts: 0 .. THREADS queue_id: queue_id_t ends: double_ptr_t ends_check: double_ptr_t new: double_ptr_t end -- local state used during reclaim() reclaim_state: record i: 0 .. THREADS j: 0 .. THREADS conflict: boolean end -- program counter values label_t: enum { -- not running any operation IDLE, -- running enqueue() ENQUEUE_BLOCK_2, ENQUEUE_BLOCK_3, ENQUEUE_BLOCK_4, ENQUEUE_BLOCK_5, ENQUEUE_BLOCK_6, ENQUEUE_BLOCK_7, ENQUEUE_BLOCK_8, ENQUEUE_BLOCK_9, ENQUEUE_BLOCK_10, ENQUEUE_BLOCK_11, -- running dequeue() DEQUEUE_BLOCK_2, DEQUEUE_BLOCK_3, DEQUEUE_BLOCK_4, DEQUEUE_BLOCK_5, DEQUEUE_BLOCK_6, DEQUEUE_BLOCK_7, DEQUEUE_BLOCK_8, -- running reclaim() RECLAIM_BLOCK_1, RECLAIM_BLOCK_2, RECLAIM_BLOCK_3, RECLAIM_BLOCK_4, RECLAIM_BLOCK_5 } -- thread-local state thread_local: record pc: label_t -- program counter head: queue_node_id_opt -- queue head being currently examined tail: queue_node_id_opt -- queue tail being currently examined enqueue_locals: enqueue_state -- state used during enqueue() dequeue_locals: dequeue_state -- state used during dequeue() reclaim_locals: reclaim_state -- state used during reclaim() deferred: array [0 .. THREADS - 1] of queue_node_id_opt -- deferred pointers to free used in reclaim() end var /* Globally addressable queue nodes. You can think of this as memory indexed * by virtual address. */ queue_nodes: array [queue_node_id] of queue_node -- IDs of free queue nodes from which malloc allocates freelist: array [queue_node_id] of boolean -- the pending queues for each thread q: array [queue_id_t] of queue -- thread-local states thread_locals: array [thread_id_t] of thread_local -- hazarded pointers hazarded: array [thread_id_t] of queue_node_id_opt function malloc(): queue_node_id_opt; begin -- try to find a free queue node /* XXX: awkward hack here where we need to loop over queue_node_id_opt instead * of queue_node_id because Murphi doesn't like us indexing freelist with a * value not of its index type. */ for i: queue_node_id_opt do if i != NULL then if freelist[i] then freelist[i] := false; return i; end; end; end; -- all queue nodes are in-use (allocated) return NULL; end procedure free(p: queue_node_id_opt); begin -- allow callers to free NULL as a no-op if p = NULL then return; end; assert !freelist[p] "freeing a queue node that was not in use"; freelist[p] := true; end -- is the given queue node in the given queue? function in_queue(qid: queue_id_t; qnid: queue_node_id): boolean; var i: queue_node_id_opt begin -- follow the linked-list of queue nodes, looking for our target i := q[qid].head; while i != NULL do if i = qnid then return true; end; i := queue_nodes[i].next; end; -- we didn't find it return false; end function double_ptr_make(head: queue_node_id_opt; tail: queue_node_id_opt): double_ptr_t; var tmp: double_ptr_t begin tmp.head := head; tmp.tail := tail; return tmp; end procedure hazard(thread_id: thread_id_t; p: queue_node_id); begin assert hazarded[thread_id] = NULL "hazarding multiple pointers at once"; hazarded[thread_id] := p; end procedure unhazard(thread_id: thread_id_t; p: queue_node_id); begin assert hazarded[thread_id] != NULL "unhazarding a pointer when none are hazarded"; assert hazarded[thread_id] = p "unhazarding a pointer that differs from the one hazarded"; hazarded[thread_id] := NULL; end procedure goto_idle(thread_id: thread_id_t); begin -- blank all function-local state undefine thread_locals[thread_id].enqueue_locals; undefine thread_locals[thread_id].dequeue_locals; undefine thread_locals[thread_id].reclaim_locals; thread_locals[thread_id].pc := IDLE; end startstate begin -- mark all queue nodes as unallocated for qnid: queue_node_id do freelist[qnid] := true; end; -- set all queues as empty for qid: queue_id_t do q[qid].head := NULL; q[qid].tail := NULL; end; -- reset all threads for thread_id: thread_id_t do goto_idle(thread_id); end; -- we start with no deferred deallocations for thread_id: thread_id_t do for i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] := NULL; end; end; -- we start with no hazarded pointers for i: thread_id_t do hazarded[i] := NULL; end; end ruleset thread_id: thread_id_t do alias pc: thread_locals[thread_id].pc do ruleset queue_id: queue_id_t do alias n: thread_locals[thread_id].enqueue_locals.n do rule "enqueue start" pc = IDLE ==> begin thread_locals[thread_id].enqueue_locals.queue_id := queue_id; n := malloc(); if n = NULL then goto_idle(thread_id); return; end; queue_nodes[n].next := NULL; pc := ENQUEUE_BLOCK_2; end end end alias ends: thread_locals[thread_id].enqueue_locals.ends ends_check: thread_locals[thread_id].enqueue_locals.ends_check head: thread_locals[thread_id].enqueue_locals.ends.head n: thread_locals[thread_id].enqueue_locals.n new: thread_locals[thread_id].enqueue_locals.new queue_id: thread_locals[thread_id].enqueue_locals.queue_id tail: thread_locals[thread_id].enqueue_locals.ends.tail do rule "enqueue block 2" pc = ENQUEUE_BLOCK_2 ==> begin ends := q[queue_id]; pc := ENQUEUE_BLOCK_3; end rule "enqueue block 3" pc = ENQUEUE_BLOCK_3 ==> begin if tail = NULL then assert head = NULL "tail of queue null while head is non-null"; new := double_ptr_make(n, n); if q[queue_id] = ends then q[queue_id] := new; goto_idle(thread_id); else ends := q[queue_id]; -- intentional lack of pc change ("goto retry") end; else hazard(thread_id, tail); pc := ENQUEUE_BLOCK_4; end; end rule "enqueue block 4" pc = ENQUEUE_BLOCK_4 ==> begin ends_check := q[queue_id]; if ends != ends_check then pc := ENQUEUE_BLOCK_5; else pc := ENQUEUE_BLOCK_6; end; end rule "enqueue block 5" pc = ENQUEUE_BLOCK_5 ==> begin unhazard(thread_id, tail); ends := ends_check; undefine ends_check; pc := ENQUEUE_BLOCK_3; end rule "enqueue block 6" pc = ENQUEUE_BLOCK_6 ==> begin if queue_nodes[tail].next = NULL then queue_nodes[tail].next := n; pc := ENQUEUE_BLOCK_8; else pc := ENQUEUE_BLOCK_7; end; end rule "enqueue block 7" pc = ENQUEUE_BLOCK_7 ==> begin unhazard(thread_id, tail); pc := ENQUEUE_BLOCK_3; end rule "enqueue block 8" pc = ENQUEUE_BLOCK_8 ==> begin new := double_ptr_make(head, n); if q[queue_id] = ends then q[queue_id] := new; pc := ENQUEUE_BLOCK_11; else -- use `ends_check` instead of `old` to save a state variable ends_check := q[queue_id]; pc := ENQUEUE_BLOCK_9; end; end rule "enqueue block 9" pc = ENQUEUE_BLOCK_9 ==> begin assert queue_nodes[tail].next = n "undo of write to tail->next failed"; queue_nodes[tail].next := NULL; pc := ENQUEUE_BLOCK_10; end rule "enqueue block 10" pc = ENQUEUE_BLOCK_10 ==> begin -- remember, using `ends_check` here where the implementation uses `old` unhazard(thread_id, tail); ends := ends_check; undefine ends_check; pc := ENQUEUE_BLOCK_3; end rule "enqueue block 11" pc = ENQUEUE_BLOCK_11 ==> begin unhazard(thread_id, tail); goto_idle(thread_id); end end ruleset queue_id: queue_id_t do rule "dequeue start" thread_locals[thread_id].pc = IDLE ==> begin thread_locals[thread_id].dequeue_locals.attempts := 0; thread_locals[thread_id].dequeue_locals.queue_id := queue_id; thread_locals[thread_id].dequeue_locals.ends := q[queue_id]; thread_locals[thread_id].pc := DEQUEUE_BLOCK_3; end end alias ends: thread_locals[thread_id].dequeue_locals.ends head: thread_locals[thread_id].dequeue_locals.ends.head tail: thread_locals[thread_id].dequeue_locals.ends.tail queue_id: thread_locals[thread_id].dequeue_locals.queue_id attempts: thread_locals[thread_id].dequeue_locals.attempts ends_check: thread_locals[thread_id].dequeue_locals.ends_check new: thread_locals[thread_id].dequeue_locals.new do rule "dequeue block 2" pc = DEQUEUE_BLOCK_2 ==> begin ends := q[queue_id]; pc := DEQUEUE_BLOCK_3; end rule "dequeue block 3" pc = DEQUEUE_BLOCK_3 ==> begin if head != NULL then hazard(thread_id, head); pc := DEQUEUE_BLOCK_4; else queue_id := (queue_id + 1) % QUEUES; attempts := attempts + 1; if attempts < QUEUES then pc := DEQUEUE_BLOCK_2; else goto_idle(thread_id); end; end; end rule "dequeue block 4" pc = DEQUEUE_BLOCK_4 ==> begin ends_check := q[queue_id]; if ends != ends_check then pc := DEQUEUE_BLOCK_5; else undefine ends_check; pc := DEQUEUE_BLOCK_6; end; end rule "dequeue block 5" pc = DEQUEUE_BLOCK_5 ==> begin unhazard(thread_id, head); ends := ends_check; undefine ends_check; pc := DEQUEUE_BLOCK_2; end rule "dequeue block 6" pc = DEQUEUE_BLOCK_6 ==> begin if head = tail then new := double_ptr_make(NULL, NULL); else new := double_ptr_make(queue_nodes[head].next, tail); end; pc := DEQUEUE_BLOCK_7; end rule "dequeue block 7" pc = DEQUEUE_BLOCK_7 ==> begin if q[queue_id] = ends then q[queue_id] := new; undefine tail; undefine new; pc := DEQUEUE_BLOCK_8; else undefine new; ends_check := q[queue_id]; pc := DEQUEUE_BLOCK_5; end; end rule "dequeue block 8" pc = DEQUEUE_BLOCK_8 ==> begin unhazard(thread_id, head); pc := RECLAIM_BLOCK_1; end end alias head: thread_locals[thread_id].dequeue_locals.ends.head i: thread_locals[thread_id].reclaim_locals.i j: thread_locals[thread_id].reclaim_locals.j conflict: thread_locals[thread_id].reclaim_locals.conflict deferred: thread_locals[thread_id].deferred do rule "reclaim block 1" pc = RECLAIM_BLOCK_1 ==> begin assert head != NULL "reclaiming a null pointer"; assert hazarded[thread_id] = NULL "reclaiming a pointer while holding a hazarded pointer"; -- compressed loop iterations until we find something interesting i := 0; while i < THREADS & deferred[i] = NULL do i := i + 1; end; if i < THREADS then conflict := false; j := 0; pc := RECLAIM_BLOCK_2; else conflict := false; i := 0; pc := RECLAIM_BLOCK_4; end; end rule "reclaim block 2" pc = RECLAIM_BLOCK_2 ==> begin assert i < THREADS; assert deferred[i] != NULL; if j = thread_id then assert hazarded[j] = NULL; elsif deferred[i] = hazarded[j] then conflict := true; j := THREADS - 1; -- "break" end; j := j + 1; if j = THREADS then undefine j; pc := RECLAIM_BLOCK_3; end; end rule "reclaim block 3" pc = RECLAIM_BLOCK_3 ==> begin if !conflict then free(deferred[i]); deferred[i] := NULL; end; undefine conflict; while i < THREADS & deferred[i] = NULL do i := i + 1; end; if i < THREADS then conflict := false; j := 0; pc := RECLAIM_BLOCK_2; else conflict := false; i := 0; pc := RECLAIM_BLOCK_4; end; end rule "reclaim block 4" pc = RECLAIM_BLOCK_4 ==> begin assert i < THREADS; if i = thread_id then assert hazarded[i] = NULL; elsif head = hazarded[i] then conflict := true; i := THREADS - 1; -- "break" end; i := i + 1; if i = THREADS then undefine i; pc := RECLAIM_BLOCK_5; end; end rule "reclaim block 5" pc = RECLAIM_BLOCK_5 ==> begin if !conflict then free(head); goto_idle(thread_id); return; else for ii: 0 .. THREADS - 1 do if deferred[ii] = NULL then deferred[ii] := head; goto_idle(thread_id); return; end; end; end; assert false "deferred more than `THREADS` reclamations"; end end end end invariant "queue only empty when head and tail agree" forall qid: queue_id_t do (q[qid].head = NULL -> q[qid].tail = NULL) & (q[qid].tail = NULL -> q[qid].head = NULL) end invariant "no memory leaks" /* at quiescence, every queue node is either in a queue, in the free list, or * in a thread's deferred deallocations list */ exists thread_id: thread_id_t do thread_locals[thread_id].pc != IDLE end | forall qnid: queue_node_id do exists qid: queue_id_t do in_queue(qid, qnid) end | freelist[qnid] | exists thread_id: thread_id_t do exists i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] = qnid end end end invariant "no double use of nodes" -- every queue node in a queue is nowhere else forall qnid: queue_node_id do forall qid: queue_id_t do in_queue(qid, qnid) -> (forall qid2: queue_id_t do qid2 = qid | !in_queue(qid2, qnid) end & !freelist[qnid] & forall thread_id: thread_id_t do forall i: 0 .. THREADS - 1 do thread_locals[thread_id].deferred[i] != qnid end end) end end rumur-2020.02.17/misc/read-raw.smt2000066400000000000000000000405531362265074000165370ustar00rootroot00000000000000; proof of correctness of read_raw() ; To prove read_raw() correct, we wish to prove that its implementation ; corresponds to a specification: ; ; spec(h.base, h.offset, h.width) == impl(h) ; ; Its specification is straightforward to define: ; ; spec(h_base, h_offset, h_width) ; = ((*(integer*)h_base) >> h_offset) & ((((integer)1) << h_width) - 1) ; ; where “integer” is an infinite precision integer type. Note however that we ; can relax this constraint. Offsets are always within the first byte (i.e. they ; are bit 0 - bit 7) and the maximum supported width is 64 bits. Therefore we ; can treat the base as pointing to a 71-bit vector, instead of to an infinite ; precision number. However, instead of using these bounds, we assume that in ; future we may support widths up to 128 bits if we have a uint128_t type ; available. So we need to treat the base as a 128 + 7 = 135-bit vector. ; ; The implementation of read_raw() can be viewed in ../rumur/resources/header.c. ; To put it into a more suitable form for SMT reasoning, we transform it into a ; branchless Static Single Assignment (SSA) form: ; ; // inline handle_align() ; size_t width = h_width + h_offset; ; size_t aligned_width = width % 8 != 0 ? width + 8 - width % 8 : width; ; ; bool u128_branch = aligned_width > (sizeof(uint64_t) - 1) * 8; ; ; size_t low_size = u128_branch ? aligned_width / 8 : 0; ; size_t low_size2 = u128_branch ? low_size > sizeof(uint128_t) ? sizeof(uint128_t) : low_size : 0; ; ; // inline copy_out128() ; uint128_t low = 0; ; u128_branch ? memcpy(&low, h_base, low_size2) : (void); ; ; uint128_t low2 = u128_branch ? low >> h_offset : 0; ; ; size_t high_size = u128_branch ? aligned_width / 8 - low_size2 : 0; ; ; // inline copy_out128() ; uint128_t high = 0; ; u128_branch ? high_size != 0 ? memcpy(&high, h_base + sizeof(low2), high_size) : (void) : (void); ; ; uint128_t high2 = u128_branch ? high_size != 0 ? high << (sizeof(low2) * 8 - h_offset) : 0 : 0; ; ; uint128_t low3 = u128_branch ? high_size != 0 ? low2 | high2 : low2 : 0; ; ; uint128_t mask = u128_branch ? h_width < sizeof(low3) * 8 ? (((uint128_t)1) << h_width) - 1 : 0 : 0; ; uint128_t low4 = u128_branch ? h_width < sizeof(low3) * 8 ? low3 & mask : low3 : 0; ; ; uint64_t ret = low4; ; ; // future possibility where we can read out >64-bit values ; uint128_t ret2 = low4; ; ; size_t low_size3 = aligned_width / 8; ; size_t low_size4 = low_size3 > sizeof(uint64_t) ? sizeof(uint64_t) : low_size3; ; ; // inline copy_out64() ; uint64_t low5 = 0; ; memcpy(&low5, h_base, low_size4); ; ; uint64_t low6 = low5 >> h_offset; ; ; size_t high_size2 = aligned_width / 8 - low_size4; ; ; // inline copy_out64() ; uint64_t high3 = 0; ; high_size2 != 0 ? memcpy(&high3, h_base + sizeof(low6), high_size2) : (void); ; uint64_t high4 = high_size2 != 0 ? high3 << (sizeof(low6) * 8 - h_offset : 0; ; ; uint64_t low7 = high_size2 != 0 ? low6 | high4 : low6; ; ; uint64_t mask2 = h_width < sizeof(low7) * 8 ? (UINT64_C(1) << h_width) - 1 : 0; ; uint64_t low8 = h_width < sizeof(low7) * 8 ? low7 & mask2 : low7; ; ; uint64_t ret3 = h_width == 0 ? 0 : low8; ; ; Now we can construct a correctness proof by transliterating both the ; specification and implementation into SMT, and then proving that they are ; equal for all valid inputs. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (set-logic QF_AUFBV) (set-option :produce-models true) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; input base, that we treat as a bitvector (declare-fun h_base () (_ BitVec 135)) ; input offset, that can be 0-7 (declare-fun h_offset () (_ BitVec 64)) (assert (bvule h_offset (_ bv7 64))) ; input width, that can be up to 64, but we future proof this to allow up to 128 (declare-fun h_width () (_ BitVec 64)) (assert (bvule h_width (_ bv128 64))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; the spec, which we can state quite simply (declare-fun spec () (_ BitVec 128)) (assert (= spec (bvand ((_ extract 127 0) (bvlshr h_base ((_ zero_extend 71) h_offset))) (bvsub (bvshl (_ bv1 128) ((_ zero_extend 64) h_width)) (_ bv1 128))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; transliterate the implementation ; size_t width = h_width + h_offset; (declare-fun width () (_ BitVec 64)) (assert (= width (bvadd h_width h_offset))) ; size_t aligned_width = width % 8 != 0 ? width + 8 - width % 8 : width; (declare-fun aligned_width () (_ BitVec 64)) (assert (= aligned_width (ite (not (= (bvsmod width (_ bv8 64)) (_ bv0 64))) (bvadd width (bvsub (_ bv8 64) (bvsmod width (_ bv8 64)))) width))) ; bool u128_branch = aligned_width > (sizeof(uint64_t) - 1) * 8; (declare-fun u128_branch () Bool) (assert (= u128_branch (bvugt aligned_width (bvmul (bvsub (_ bv8 64) (_ bv1 64)) (_ bv8 64))))) ; size_t low_size = u128_branch ? aligned_width / 8 : 0; (declare-fun low_size () (_ BitVec 64)) (assert (= low_size (ite u128_branch (bvudiv aligned_width (_ bv8 64)) (_ bv0 64)))) ; size_t low_size2 = u128_branch ? low_size > sizeof(uint128_t) ? sizeof(uint128_t) : low_size : 0; (declare-fun low_size2 () (_ BitVec 64)) (assert (= low_size2 (ite u128_branch (ite (bvugt low_size (_ bv16 64)) (_ bv16 64) low_size) (_ bv0 64)))) ; u128_branch ? memcpy(&low, h_base, low_size2) : (void); (declare-fun low () (_ BitVec 128)) (assert (= low (ite u128_branch (bvor (ite (bvugt low_size2 (_ bv0 64)) ((_ zero_extend 120) ((_ extract 7 0) h_base)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv1 64)) (bvshl ((_ zero_extend 120) ((_ extract 15 8) h_base)) (_ bv8 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv2 64)) (bvshl ((_ zero_extend 120) ((_ extract 23 16) h_base)) (_ bv16 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv3 64)) (bvshl ((_ zero_extend 120) ((_ extract 31 24) h_base)) (_ bv24 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv4 64)) (bvshl ((_ zero_extend 120) ((_ extract 39 32) h_base)) (_ bv32 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv5 64)) (bvshl ((_ zero_extend 120) ((_ extract 47 40) h_base)) (_ bv40 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv6 64)) (bvshl ((_ zero_extend 120) ((_ extract 55 48) h_base)) (_ bv48 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv7 64)) (bvshl ((_ zero_extend 120) ((_ extract 63 56) h_base)) (_ bv56 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv8 64)) (bvshl ((_ zero_extend 120) ((_ extract 71 64) h_base)) (_ bv64 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv9 64)) (bvshl ((_ zero_extend 120) ((_ extract 79 72) h_base)) (_ bv72 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv10 64)) (bvshl ((_ zero_extend 120) ((_ extract 87 80) h_base)) (_ bv80 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv11 64)) (bvshl ((_ zero_extend 120) ((_ extract 95 88) h_base)) (_ bv88 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv12 64)) (bvshl ((_ zero_extend 120) ((_ extract 103 96) h_base)) (_ bv96 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv13 64)) (bvshl ((_ zero_extend 120) ((_ extract 111 104) h_base)) (_ bv104 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv14 64)) (bvshl ((_ zero_extend 120) ((_ extract 119 112) h_base)) (_ bv112 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv15 64)) (bvshl ((_ zero_extend 120) ((_ extract 127 120) h_base)) (_ bv120 128)) (_ bv0 128))) (_ bv0 128)))) ; uint128_t low2 = u128_branch ? low >> h_offset : 0; (declare-fun low2 () (_ BitVec 128)) (assert (= low2 (ite u128_branch (bvlshr low ((_ zero_extend 64) h_offset)) (_ bv0 128)))) ; size_t high_size = u128_branch ? aligned_width / 8 - low_size2 : 0; (declare-fun high_size () (_ BitVec 64)) (assert (= high_size (ite u128_branch (bvsub (bvudiv aligned_width (_ bv8 64)) low_size2) (_ bv0 64)))) ; u128_branch ? high_size != 0 ? memcpy(&high, h_base + sizeof(low2), high_size) : (void) : (void); (declare-fun high () (_ BitVec 128)) (assert (= high (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvor (ite (bvugt high_size (_ bv0 64)) (bvshl ((_ zero_extend 120) ((_ extract 7 0) (bvlshr h_base (_ bv128 135)))) (_ bv0 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv1 64)) (bvshl ((_ zero_extend 120) ((_ extract 15 8) (bvlshr h_base (_ bv128 135)))) (_ bv8 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv2 64)) (bvshl ((_ zero_extend 120) ((_ extract 23 16) (bvlshr h_base (_ bv128 135)))) (_ bv16 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv3 64)) (bvshl ((_ zero_extend 120) ((_ extract 31 24) (bvlshr h_base (_ bv128 135)))) (_ bv24 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv4 64)) (bvshl ((_ zero_extend 120) ((_ extract 39 32) (bvlshr h_base (_ bv128 135)))) (_ bv32 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv5 64)) (bvshl ((_ zero_extend 120) ((_ extract 47 40) (bvlshr h_base (_ bv128 135)))) (_ bv40 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv6 64)) (bvshl ((_ zero_extend 120) ((_ extract 55 48) (bvlshr h_base (_ bv128 135)))) (_ bv48 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv7 64)) (bvshl ((_ zero_extend 120) ((_ extract 63 56) (bvlshr h_base (_ bv128 135)))) (_ bv56 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv8 64)) (bvshl ((_ zero_extend 120) ((_ extract 71 64) (bvlshr h_base (_ bv128 135)))) (_ bv64 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv9 64)) (bvshl ((_ zero_extend 120) ((_ extract 79 72) (bvlshr h_base (_ bv128 135)))) (_ bv72 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv10 64)) (bvshl ((_ zero_extend 120) ((_ extract 87 80) (bvlshr h_base (_ bv128 135)))) (_ bv80 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv11 64)) (bvshl ((_ zero_extend 120) ((_ extract 95 88) (bvlshr h_base (_ bv128 135)))) (_ bv88 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv12 64)) (bvshl ((_ zero_extend 120) ((_ extract 103 96) (bvlshr h_base (_ bv128 135)))) (_ bv96 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv13 64)) (bvshl ((_ zero_extend 120) ((_ extract 111 104) (bvlshr h_base (_ bv128 135)))) (_ bv104 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv14 64)) (bvshl ((_ zero_extend 120) ((_ extract 119 112) (bvlshr h_base (_ bv128 135)))) (_ bv112 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv15 64)) (bvshl ((_ zero_extend 120) ((_ extract 127 120) (bvlshr h_base (_ bv128 135)))) (_ bv120 128)) (_ bv0 128))) (_ bv0 128)) (_ bv0 128)))) ; uint128_t high2 = u128_branch ? high_size != 0 ? high << (sizeof(low2) * 8 - h_offset) : 0 : 0; (declare-fun high2 () (_ BitVec 128)) (assert (= high2 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvshl high (bvsub (bvmul (_ bv16 128) (_ bv8 128)) ((_ zero_extend 64) h_offset))) (_ bv0 128)) (_ bv0 128)))) ; uint128_t low3 = u128_branch ? high_size != 0 ? low2 | high2 : low2 : 0; (declare-fun low3 () (_ BitVec 128)) (assert (= low3 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvor low2 high2) low2) (_ bv0 128)))) ; uint128_t mask = u128_branch ? h_width < sizeof(low3) * 8 ? (((uint128_t)1) << h_width) - 1 : 0 : 0; (declare-fun mask () (_ BitVec 128)) (assert (= mask (ite u128_branch (ite (bvult h_width (bvmul (_ bv16 64) (_ bv8 64))) (bvsub (bvshl (_ bv1 128) ((_ zero_extend 64) h_width)) (_ bv1 128)) (_ bv0 128)) (_ bv0 128)))) ; uint128_t low4 = u128_branch ? h_width < sizeof(low3) * 8 ? low3 & mask : low3 : 0; (declare-fun low4 () (_ BitVec 128)) (assert (= low4 (ite u128_branch (ite (bvult h_width (bvmul (_ bv16 64) (_ bv8 64))) (bvand low3 mask) low3) (_ bv0 128)))) ; uint64_t ret = low4; (declare-fun ret () (_ BitVec 64)) (assert (= ret ((_ extract 63 0) low4))) ; uint128_t ret2 = low4; (declare-fun ret2 () (_ BitVec 128)) (assert (= ret2 low4)) ; low_size3 = aligned.width / 8 (declare-fun low_size3 () (_ BitVec 64)) (assert (= low_size3 (bvudiv aligned_width (_ bv8 64)))) ; size_t low_size4 = low_size3 > sizeof(uint64_t) ? sizeof(uint64_t) : low_size3; (declare-fun low_size4 () (_ BitVec 64)) (assert (= low_size4 (ite (bvugt low_size3 (_ bv8 64)) (_ bv8 64) low_size3))) ; memcpy(&low5, h_base, low_size4); (declare-fun low5 () (_ BitVec 64)) (assert (= low5 (bvor (ite (bvugt low_size4 (_ bv0 64)) ((_ zero_extend 56) ((_ extract 7 0) h_base)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv1 64)) (bvshl ((_ zero_extend 56) ((_ extract 15 8) h_base)) (_ bv8 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv2 64)) (bvshl ((_ zero_extend 56) ((_ extract 23 16) h_base)) (_ bv16 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv3 64)) (bvshl ((_ zero_extend 56) ((_ extract 31 24) h_base)) (_ bv24 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv4 64)) (bvshl ((_ zero_extend 56) ((_ extract 39 32) h_base)) (_ bv32 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv5 64)) (bvshl ((_ zero_extend 56) ((_ extract 47 40) h_base)) (_ bv40 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv6 64)) (bvshl ((_ zero_extend 56) ((_ extract 55 48) h_base)) (_ bv48 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv7 64)) (bvshl ((_ zero_extend 56) ((_ extract 63 56) h_base)) (_ bv56 64)) (_ bv0 64))))) ; uint64_t low6 = low5 >> h_offset; (declare-fun low6 () (_ BitVec 64)) (assert (= low6 (bvlshr low5 h_offset))) ; size_t high_size2 = aligned_width / 8 - low_size4; (declare-fun high_size2 () (_ BitVec 64)) (assert (= high_size2 (bvsub (bvudiv aligned_width (_ bv8 64)) low_size4))) ; high_size2 != 0 ? memcpy(&high3, h_base + sizeof(low6), high_size2) : (void); (declare-fun high3 () (_ BitVec 64)) (assert (= high3 (ite (not (= high_size2 (_ bv0 64))) (bvor (ite (bvugt high_size2 (_ bv0 64)) ((_ zero_extend 56) ((_ extract 7 0) (bvlshr h_base (_ bv64 135)))) (_ bv0 64)) (ite (bvugt high_size2 (_ bv1 64)) (bvshl ((_ zero_extend 56) ((_ extract 15 8) (bvlshr h_base (_ bv64 135)))) (_ bv8 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv2 64)) (bvshl ((_ zero_extend 56) ((_ extract 23 16) (bvlshr h_base (_ bv64 135)))) (_ bv16 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv3 64)) (bvshl ((_ zero_extend 56) ((_ extract 31 24) (bvlshr h_base (_ bv64 135)))) (_ bv24 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv4 64)) (bvshl ((_ zero_extend 56) ((_ extract 39 32) (bvlshr h_base (_ bv64 135)))) (_ bv32 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv5 64)) (bvshl ((_ zero_extend 56) ((_ extract 47 40) (bvlshr h_base (_ bv64 135)))) (_ bv40 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv6 64)) (bvshl ((_ zero_extend 56) ((_ extract 55 48) (bvlshr h_base (_ bv64 135)))) (_ bv48 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv7 64)) (bvshl ((_ zero_extend 56) ((_ extract 63 56) (bvlshr h_base (_ bv64 135)))) (_ bv56 64)) (_ bv0 64))) (_ bv0 64)))) ; uint64_t high4 = high_size2 != 0 ? high3 << (sizeof(low6) * 8 - h_offset : 0; (declare-fun high4 () (_ BitVec 64)) (assert (= high4 (ite (not (= high_size2 (_ bv0 64))) (bvshl high3 (bvsub (bvmul (_ bv8 64) (_ bv8 64)) h_offset)) (_ bv0 64)))) ; uint64_t low7 = high_size2 != 0 ? low6 | high4 : low6; (declare-fun low7 () (_ BitVec 64)) (assert (= low7 (ite (not (= high_size2 (_ bv0 64))) (bvor low6 high4) low6))) ; uint64_t mask2 = h_width < sizeof(low7) * 8 ? (UINT64_C(1) << h_width) - 1 : 0; (declare-fun mask2 () (_ BitVec 64)) (assert (= mask2 (ite (bvult h_width (bvmul (_ bv8 64) (_ bv8 64))) (bvsub (bvshl (_ bv1 64) h_width) (_ bv1 64)) (bvnot (_ bv0 64))))) ; uint64_t low8 = h_width < sizeof(low7) * 8 ? low7 & mask2 : low7; (declare-fun low8 () (_ BitVec 64)) (assert (= low8 (ite (bvult h_width (bvmul (_ bv8 64) (_ bv8 64))) (bvand low7 mask2) low7))) ; uint64_t ret3 = h_width == 0 ? 0 : low8; (declare-fun ret3 () (_ BitVec 64)) (assert (= ret3 (ite (= h_width (_ bv0 64)) (_ bv0 64) low8))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; attempt to find a case where the specification and the value returned by the ; implementation differ (assert (not (and ; the uint128_t with uint64_t return path only returns the correct answer for ; 57-64-bit unpacks (or (not u128_branch) (bvugt h_width (_ bv64 64)) (= ((_ extract 63 0) spec) ret)) ; the uint128_t with uint128_t return path is correct for all widths when ; enabled (or (not u128_branch) (= spec ret2)) ; the uint64_t path returns the correct answer for widths ≤64 (or (bvugt h_width (_ bv64 64)) (= ((_ extract 63 0) spec) ret3)) ))) (check-sat) ;(get-model) rumur-2020.02.17/misc/seccomp-supported.sh000077500000000000000000000030161362265074000202320ustar00rootroot00000000000000#!/usr/bin/env bash # check whether seccomp sandboxing is supported on this platform # # This is useful for discriminating platforms where seccomp is unavailable or # configured out. # assume seccomp is unavailable on non-Linux platforms if [ "$(uname -s)" != "Linux" ]; then exit 1 fi # create a temporary space to work in TMP=$(mktemp -d) pushd ${TMP} # create a sandbox testing program cat - >test.c < #include #include #include #include #include #include #include int main(void) { // disable addition of new privileges int r = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); if (r != 0) { perror("prctl(PR_SET_NO_NEW_PRIVS) failed"); return EXIT_FAILURE; } // a BPF program that allows everything static struct sock_filter filter[] = { // return allow BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), }; static const struct sock_fprog filter_program = { .len = sizeof(filter) / sizeof(filter[0]), .filter = filter, }; // apply the filter to ourselves r = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &filter_program, 0, 0); if (r != 0) { perror("prctl(PR_SET_SECCOMP) failed"); return EXIT_FAILURE; } return EXIT_SUCCESS; } EOT # compile the test program if ! ${CC:-cc} -std=c11 test.c; then popd rm -rf "${TMP}" exit 1 fi # execute the test program if ! ./a.out; then popd rm -rf "${TMP}" exit 1 fi # clean up popd rm -rf "${TMP}" exit 0 rumur-2020.02.17/misc/verifier.rng000066400000000000000000000062261362265074000165500ustar00rootroot00000000000000 rumur-2020.02.17/misc/write-raw.smt2000066400000000000000000000745311362265074000167610ustar00rootroot00000000000000; proof of correctness of write_raw() ; Similar to read-raw.smt2, the following proves that write_raw() does what it ; is intended to do. Unlike the read_raw() proof, we cannot write a simple ; equivalence to describe write_raw()’s correctness because we want to prove ; multiple things. Namely, (1) it writes the given value to the target byte ; buffer and (2) it does not overwrite any unrelated bits. ; ; The implementation of write_raw() can be viewed in ; ../rumur/resources/header.c. To begin with, we transform it into a branchless ; Static Single Assignment (SSA) form: ; ; // inline handle_align() ; size_t width = h_width + h_offset; ; size_t aligned_width = width % 8 != 0 ? width + 8 - width % 8 : width; ; ; bool u128_branch = defined(__SIZEOF_INT128__) && aligned_width > (sizeof(uint64_t) - 1) * 8; ; ; size_t low_size = u128_branch ? aligned_width / 8 : 0; ; size_t low_size2 = u128_branch ? low_size > sizeof(uint128_t) ? sizeof(uint128_t) : low_size : 0; ; ; // inline copy_out128() ; uint128_t low = 0; ; u128_branch ? memcpy(&low, h_base, low_size2) : (void); ; ; uint128_t or_mask = u128_branch ? ((uint128_t)v) << h_offset : 0; ; uint128_t or_mask2 = u128_branch ? low_size2 < sizeof(low) ? or_mask & ((((uint128_t)1) << (low_size2 * 8)) - 1) : or_mask : 0; ; ; uint128_t and_mask = u128_branch ? (((uint128_t)1) << h_offset) - 1 : 0; ; size_t high_bits = u128_branch ? h_width + h_offset < sizeof(low) * 8 ? aligned_width - h_offset - h_width : 0 : 0; ; uint128_t and_mask2 = u128_branch ? h_width + h_offset < sizeof(low) * 8 ? and_mask | (((((uint128_t)1) << high_bits) - 1) << (low_size2 * 8 - high_bits)) : and_mask : 0; ; ; uint128_t low2 = u128_branch ? (low & and_mask2) | or_mask2 : 0; ; ; // inline copy_in128() ; void *h_base2 = h_base; ; u128_branch ? memcpy(h_base2, &low2, low_size2) : (void); ; ; size_t high_size = u128_branch ? aligned_width / 8 - low_size2 : 0; ; ; // inline copy_out128() ; uint128_t high = 0; ; u128_branch ? high_size != 0 ? memcpy(&high, h_base + sizeof(low2), high_size) : (void) : (void); ; ; uint128_t or_mask3 = u128_branch ? high_size != 0 ? ((uint128_t)v) >> (sizeof(low2) * 8 - h_offset) : 0 : 0; ; uint128_t and_mask3 = u128_branch ? high_size != 0 ? ~((((uint128_t)1) << (h_width + h_offset - sizeof(low2) * 8)) - 1) : 0 : 0; ; ; uint128_t high2 = u128_branch ? high_size != 0 ? (high & and_mask3) | or_mask3 : 0 : 0; ; ; // inline copy_in128() ; void *h_base3 = h_base2; ; u128_branch ? high_size != 0 ? memcpy(h_base3 + sizeof(low2), &high2, high_size) : (void) : (void); ; ; // return from u128_branch ; ; // uint64_t logic follows ; ; size_t low_size3 = u128_branch ? 0 : aligned_width / 8; ; size_t low_size4 = u128_branch ? 0 : low_size3 > sizeof(uint64_t) ? sizeof(uint64_t) : low_size3; ; ; // inline copy_out64() ; uint64_t low3 = 0; ; u128_branch ? (void) : memcpy(&low3, h_base, low_size4); ; ; uint64_t or_mask4 = u128_branch ? 0 : ((uint64_t)v) << h_offset; ; uint64_t or_mask5 = u128_branch ? 0 : low_size4 < sizeof(low3) ? or_mask4 & ((UINT64_C(1) << (low_size4 * 8)) - 1) : or_mask4; ; ; uint64_t and_mask4 = u128_branch ? 0 : (UINT64_C(1) << h_offset) - 1; ; size_t high_bits2 = u128_branch ? 0 : h_width + h_offset < sizeof(low3) * 8 ? aligned_width - h_offset - h_width : 0; ; uint64_t and_mask5 = u128_branch ? 0 : h_wdith + h_offset < sizeof(low3) * 8 ? and_mask4 | (((UINT64_C(1) << high_bits2) - 1) << (low_size4 * 8 - high_bits2)) : and_mask4; ; ; uint64_t low4 = u128_branch ? 0 : (low3 & and_mask5) | or_mask5; ; ; // inline copy_in64() ; void *h_base4 = h_base; ; u128_branch ? (void) : memcpy(h_base4, &low4, low_size4); ; ; size_t high_size2 = u128_branch ? 0 : aligned_width / 8 - low_size4; ; ; // inline copy_out64() ; uint64_t high3 = 0; ; u128_branch ? (void) : high_size2 != 0 ? memcpy(&high3, h_base4 + sizeof(low4), high_size2) : (void); ; ; uint64_t or_mask6 = u128_branch ? 0 : high_size2 != 0 ? ((uint64_t)v) >> (sizeof(low4) * 8 - h_offset) : 0; ; uint64_t and_mask6 = u128_branch ? 0 : high_size2 != 0 ? ~((UINT64_C(1) << (h_width + h_offset - sizeof(low4) * 8)) - 1) : 0; ; ; uint64_t high4 = u128_branch ? 0 : high_size2 != 0 ? (high3 & and_mask6) | or_mask6 : 0; ; ; // inline copy_in64() ; void *h_base5 = h_base4; ; u128_branch ? (void) : high_size2 != 0 ? memcpy(h_base5 + sizeof(low4), &high4, high_size2) : (void); ; ; Now we can start translating to SMT. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (set-logic QF_AUFBV) (set-option :produce-models true) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; input value, that we treat as 128-bit to be future proof (declare-fun v () (_ BitVec 128)) ; input base, that we treat as a bitvector (declare-fun h_base () (_ BitVec 136)) ; input offset, that can be 0-7 (declare-fun h_offset () (_ BitVec 64)) (assert (bvule h_offset (_ bv7 64))) ; input width, that can be up to 64, but we future proof this to allow up to 128 (declare-fun h_width () (_ BitVec 64)) (assert (bvule h_width (_ bv128 64))) ; treat whether we have a 128-bit type as an input (declare-fun defined__SIZEOF_INT128__ () Bool) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; We cannot yet describe the spec, as we need to refer to the final value of ; h_base. Defer this to after we have expressed the logic of write_raw(). ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; translate this ; ; /* sanitise input value */ ; if (h.width < sizeof(v) * 8) { ; v &= (UINT64_C(1) << h.width) - 1; ; } ; ; into an assumption on the input (assert (bvult v (bvshl (_ bv1 128) ((_ zero_extend 64) h_width)))) ; now transliterate the rest of the implementation ; size_t width = h_width + h_offset; (declare-fun width () (_ BitVec 64)) (assert (= width (bvadd h_width h_offset))) ; size_t aligned_width = width % 8 != 0 ? width + 8 - width % 8 : width; (declare-fun aligned_width () (_ BitVec 64)) (assert (= aligned_width (ite (not (= (bvsmod width (_ bv8 64)) (_ bv0 64))) (bvadd width (bvsub (_ bv8 64) (bvsmod width (_ bv8 64)))) width))) ; bool u128_branch = defined(__SIZEOF_INT128__) && aligned_width > (sizeof(uint64_t) - 1) * 8; (declare-fun u128_branch () Bool) (assert (= u128_branch (and defined__SIZEOF_INT128__ (bvugt aligned_width (bvmul (bvsub (_ bv8 64) (_ bv1 64)) (_ bv8 64)))))) ; size_t low_size = u128_branch ? aligned_width / 8 : 0; (declare-fun low_size () (_ BitVec 64)) (assert (= low_size (ite u128_branch (bvudiv aligned_width (_ bv8 64)) (_ bv0 64)))) ; size_t low_size2 = u128_branch ? low_size > sizeof(uint128_t) ? sizeof(uint128_t) : low_size : 0; (declare-fun low_size2 () (_ BitVec 64)) (assert (= low_size2 (ite u128_branch (ite (bvugt low_size (_ bv16 64)) (_ bv16 64) low_size) (_ bv0 64)))) ; uint128_t low = 0; (declare-fun low () (_ BitVec 128)) (assert (= low (ite u128_branch (bvor (ite (bvugt low_size2 (_ bv0 64)) (bvshl ((_ zero_extend 120) ((_ extract 7 0) h_base)) (_ bv0 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv1 64)) (bvshl ((_ zero_extend 120) ((_ extract 15 8) h_base)) (_ bv8 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv2 64)) (bvshl ((_ zero_extend 120) ((_ extract 23 16) h_base)) (_ bv16 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv3 64)) (bvshl ((_ zero_extend 120) ((_ extract 31 24) h_base)) (_ bv24 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv4 64)) (bvshl ((_ zero_extend 120) ((_ extract 39 32) h_base)) (_ bv32 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv5 64)) (bvshl ((_ zero_extend 120) ((_ extract 47 40) h_base)) (_ bv40 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv6 64)) (bvshl ((_ zero_extend 120) ((_ extract 55 48) h_base)) (_ bv48 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv7 64)) (bvshl ((_ zero_extend 120) ((_ extract 63 56) h_base)) (_ bv56 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv8 64)) (bvshl ((_ zero_extend 120) ((_ extract 71 64) h_base)) (_ bv64 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv9 64)) (bvshl ((_ zero_extend 120) ((_ extract 79 72) h_base)) (_ bv72 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv10 64)) (bvshl ((_ zero_extend 120) ((_ extract 87 80) h_base)) (_ bv80 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv11 64)) (bvshl ((_ zero_extend 120) ((_ extract 95 88) h_base)) (_ bv88 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv12 64)) (bvshl ((_ zero_extend 120) ((_ extract 103 96) h_base)) (_ bv96 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv13 64)) (bvshl ((_ zero_extend 120) ((_ extract 111 104) h_base)) (_ bv104 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv14 64)) (bvshl ((_ zero_extend 120) ((_ extract 119 112) h_base)) (_ bv112 128)) (_ bv0 128)) (ite (bvugt low_size2 (_ bv15 64)) (bvshl ((_ zero_extend 120) ((_ extract 127 120) h_base)) (_ bv120 128)) (_ bv0 128))) (_ bv0 128)))) ; uint128_t or_mask = u128_branch ? ((uint128_t)v) << h_offset : 0; (declare-fun or_mask () (_ BitVec 128)) (assert (= or_mask (ite u128_branch (bvshl v ((_ zero_extend 64) h_offset)) (_ bv0 128)))) ; uint128_t or_mask2 = u128_branch ? low_size2 < sizeof(low) ? or_mask & ((((uint128_t)1) << (low_size2 * 8)) - 1) : or_mask : 0; (declare-fun or_mask2 () (_ BitVec 128)) (assert (= or_mask2 (ite u128_branch (ite (bvult low_size2 (_ bv16 64)) (bvand or_mask (bvsub (bvshl (_ bv1 128) (bvmul ((_ zero_extend 64) low_size2) (_ bv8 128))) (_ bv1 128))) or_mask) (_ bv0 128)))) ; uint128_t and_mask = u128_branch ? (((uint128_t)1) << h_offset) - 1 : 0; (declare-fun and_mask () (_ BitVec 128)) (assert (= and_mask (ite u128_branch (bvsub (bvshl (_ bv1 128) ((_ zero_extend 64) h_offset)) (_ bv1 128)) (_ bv0 128)))) ; size_t high_bits = u128_branch ? h_width + h_offset < sizeof(low) * 8 ? aligned_width - h_offset - h_width : 0 : 0; (declare-fun high_bits () (_ BitVec 64)) (assert (= high_bits (ite u128_branch (ite (bvult (bvadd h_width h_offset) (bvmul (_ bv16 64) (_ bv8 64))) (bvsub (bvsub aligned_width h_offset) h_width) (_ bv0 64)) (_ bv0 64)))) ; uint128_t and_mask2 = u128_branch ? h_width + h_offset < sizeof(low) * 8 ? and_mask | (((((uint128_t)1) << high_bits) - 1) << (low_size2 * 8 - high_bits)) : and_mask : 0; (declare-fun and_mask2 () (_ BitVec 128)) (assert (= and_mask2 (ite u128_branch (ite (bvult (bvadd h_width h_offset) (bvmul (_ bv16 64) (_ bv8 64))) (bvor and_mask (bvshl (bvsub (bvshl (_ bv1 128) ((_ zero_extend 64) high_bits)) (_ bv1 128)) (bvsub (bvmul ((_ zero_extend 64) low_size2) (_ bv8 128)) ((_ zero_extend 64) high_bits)))) and_mask) (_ bv0 128)))) ; uint128_t low2 = u128_branch ? (low & and_mask2) | or_mask2 : 0; (declare-fun low2 () (_ BitVec 128)) (assert (= low2 (ite u128_branch (bvor (bvand low and_mask2) or_mask2) (_ bv0 128)))) ; u128_branch ? memcpy(h_base2, &low2, low_size2) : (void); (declare-fun h_base2 () (_ BitVec 136)) (assert (= h_base2 (ite u128_branch (bvor (ite (bvugt low_size2 (_ bv0 64)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) low2)) (_ bv0 136)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) h_base)) (_ bv0 136))) (ite (bvugt low_size2 (_ bv1 64)) (bvshl ((_ zero_extend 128) ((_ extract 15 8) low2)) (_ bv8 136)) (bvshl ((_ zero_extend 128) ((_ extract 15 8) h_base)) (_ bv8 136))) (ite (bvugt low_size2 (_ bv2 64)) (bvshl ((_ zero_extend 128) ((_ extract 23 16) low2)) (_ bv16 136)) (bvshl ((_ zero_extend 128) ((_ extract 23 16) h_base)) (_ bv16 136))) (ite (bvugt low_size2 (_ bv3 64)) (bvshl ((_ zero_extend 128) ((_ extract 31 24) low2)) (_ bv24 136)) (bvshl ((_ zero_extend 128) ((_ extract 31 24) h_base)) (_ bv24 136))) (ite (bvugt low_size2 (_ bv4 64)) (bvshl ((_ zero_extend 128) ((_ extract 39 32) low2)) (_ bv32 136)) (bvshl ((_ zero_extend 128) ((_ extract 39 32) h_base)) (_ bv32 136))) (ite (bvugt low_size2 (_ bv5 64)) (bvshl ((_ zero_extend 128) ((_ extract 47 40) low2)) (_ bv40 136)) (bvshl ((_ zero_extend 128) ((_ extract 47 40) h_base)) (_ bv40 136))) (ite (bvugt low_size2 (_ bv6 64)) (bvshl ((_ zero_extend 128) ((_ extract 55 48) low2)) (_ bv48 136)) (bvshl ((_ zero_extend 128) ((_ extract 55 48) h_base)) (_ bv48 136))) (ite (bvugt low_size2 (_ bv7 64)) (bvshl ((_ zero_extend 128) ((_ extract 63 56) low2)) (_ bv56 136)) (bvshl ((_ zero_extend 128) ((_ extract 63 56) h_base)) (_ bv56 136))) (ite (bvugt low_size2 (_ bv8 64)) (bvshl ((_ zero_extend 128) ((_ extract 71 64) low2)) (_ bv64 136)) (bvshl ((_ zero_extend 128) ((_ extract 71 64) h_base)) (_ bv64 136))) (ite (bvugt low_size2 (_ bv9 64)) (bvshl ((_ zero_extend 128) ((_ extract 79 72) low2)) (_ bv72 136)) (bvshl ((_ zero_extend 128) ((_ extract 79 72) h_base)) (_ bv72 136))) (ite (bvugt low_size2 (_ bv10 64)) (bvshl ((_ zero_extend 128) ((_ extract 87 80) low2)) (_ bv80 136)) (bvshl ((_ zero_extend 128) ((_ extract 87 80) h_base)) (_ bv80 136))) (ite (bvugt low_size2 (_ bv11 64)) (bvshl ((_ zero_extend 128) ((_ extract 95 88) low2)) (_ bv88 136)) (bvshl ((_ zero_extend 128) ((_ extract 95 88) h_base)) (_ bv88 136))) (ite (bvugt low_size2 (_ bv12 64)) (bvshl ((_ zero_extend 128) ((_ extract 103 96) low2)) (_ bv96 136)) (bvshl ((_ zero_extend 128) ((_ extract 103 96) h_base)) (_ bv96 136))) (ite (bvugt low_size2 (_ bv13 64)) (bvshl ((_ zero_extend 128) ((_ extract 111 104) low2)) (_ bv104 136)) (bvshl ((_ zero_extend 128) ((_ extract 111 104) h_base)) (_ bv104 136))) (ite (bvugt low_size2 (_ bv14 64)) (bvshl ((_ zero_extend 128) ((_ extract 119 112) low2)) (_ bv112 136)) (bvshl ((_ zero_extend 128) ((_ extract 119 112) h_base)) (_ bv112 136))) (ite (bvugt low_size2 (_ bv15 64)) (bvshl ((_ zero_extend 128) ((_ extract 127 120) low2)) (_ bv120 136)) (bvshl ((_ zero_extend 128) ((_ extract 127 120) h_base)) (_ bv120 136))) (bvshl ((_ zero_extend 128) ((_ extract 135 128) h_base)) (_ bv128 136))) (_ bv0 136)))) ; size_t high_size = u128_branch ? aligned_width / 8 - low_size2 : 0; (declare-fun high_size () (_ BitVec 64)) (assert (= high_size (ite u128_branch (bvsub (bvudiv aligned_width (_ bv8 64)) low_size2) (_ bv0 64)))) ; u128_branch ? high_size != 0 ? memcpy(&high, h_base + sizeof(low2), high_size) : (void) : (void); (declare-fun high () (_ BitVec 128)) (assert (= high (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvor (ite (bvugt high_size (_ bv0 64)) (bvshl ((_ zero_extend 120) ((_ extract 7 0) (bvlshr h_base2 (_ bv128 136)))) (_ bv0 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv1 64)) (bvshl ((_ zero_extend 120) ((_ extract 15 8) (bvlshr h_base2 (_ bv128 136)))) (_ bv8 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv2 64)) (bvshl ((_ zero_extend 120) ((_ extract 23 16) (bvlshr h_base2 (_ bv128 136)))) (_ bv16 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv3 64)) (bvshl ((_ zero_extend 120) ((_ extract 31 24) (bvlshr h_base2 (_ bv128 136)))) (_ bv24 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv4 64)) (bvshl ((_ zero_extend 120) ((_ extract 39 32) (bvlshr h_base2 (_ bv128 136)))) (_ bv32 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv5 64)) (bvshl ((_ zero_extend 120) ((_ extract 47 40) (bvlshr h_base2 (_ bv128 136)))) (_ bv40 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv6 64)) (bvshl ((_ zero_extend 120) ((_ extract 55 48) (bvlshr h_base2 (_ bv128 136)))) (_ bv48 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv7 64)) (bvshl ((_ zero_extend 120) ((_ extract 63 56) (bvlshr h_base2 (_ bv128 136)))) (_ bv56 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv8 64)) (bvshl ((_ zero_extend 120) ((_ extract 71 64) (bvlshr h_base2 (_ bv128 136)))) (_ bv64 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv9 64)) (bvshl ((_ zero_extend 120) ((_ extract 79 72) (bvlshr h_base2 (_ bv128 136)))) (_ bv72 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv10 64)) (bvshl ((_ zero_extend 120) ((_ extract 87 80) (bvlshr h_base2 (_ bv128 136)))) (_ bv80 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv11 64)) (bvshl ((_ zero_extend 120) ((_ extract 95 88) (bvlshr h_base2 (_ bv128 136)))) (_ bv88 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv12 64)) (bvshl ((_ zero_extend 120) ((_ extract 103 96) (bvlshr h_base2 (_ bv128 136)))) (_ bv96 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv13 64)) (bvshl ((_ zero_extend 120) ((_ extract 111 104) (bvlshr h_base2 (_ bv128 136)))) (_ bv104 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv14 64)) (bvshl ((_ zero_extend 120) ((_ extract 119 112) (bvlshr h_base2 (_ bv128 136)))) (_ bv112 128)) (_ bv0 128)) (ite (bvugt high_size (_ bv15 64)) (bvshl ((_ zero_extend 120) ((_ extract 127 120) (bvlshr h_base2 (_ bv128 136)))) (_ bv120 128)) (_ bv0 128))) (_ bv0 128)) (_ bv0 128)))) ; uint128_t or_mask3 = u128_branch ? high_size != 0 ? ((uint128_t)v) >> (sizeof(low2) * 8 - h_offset) : 0 : 0; (declare-fun or_mask3 () (_ BitVec 128)) (assert (= or_mask3 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvlshr v (bvsub (bvmul (_ bv16 128) (_ bv8 128)) ((_ zero_extend 64) h_offset))) (_ bv0 128)) (_ bv0 128)))) ; uint128_t and_mask3 = u128_branch ? high_size != 0 ? ~((((uint128_t)1) << (h_width + h_offset - sizeof(low2) * 8)) - 1) : 0 : 0; (declare-fun and_mask3 () (_ BitVec 128)) (assert (= and_mask3 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvnot (bvsub (bvshl (_ bv1 128) ((_ zero_extend 64) (bvsub (bvadd h_width h_offset) (_ bv128 64)))) (_ bv1 128))) (_ bv0 128)) (_ bv0 128)))) ; uint128_t high2 = u128_branch ? high_size != 0 ? (high & and_mask3) | or_mask3 : 0 : 0; (declare-fun high2 () (_ BitVec 128)) (assert (= high2 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvor (bvand high and_mask3) or_mask3) (_ bv0 128)) (_ bv0 128)))) ; u128_branch ? high_size != 0 ? memcpy(h_base + sizeof(low2), &high2, high_size) : (void) : (void); (declare-fun h_base3 () (_ BitVec 136)) (assert (= h_base3 (ite u128_branch (ite (not (= high_size (_ bv0 64))) (bvor ((_ zero_extend 8) ((_ extract 127 0) h_base2)) (ite (bvugt high_size (_ bv0 64)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) high2)) (_ bv128 136)) (bvshl ((_ zero_extend 128) ((_ extract 135 128) h_base2)) (_ bv128 136)))) h_base2) (_ bv0 136)))) ; size_t low_size3 = u128_branch ? 0 : aligned_width / 8; (declare-fun low_size3 () (_ BitVec 64)) (assert (= low_size3 (ite u128_branch (_ bv0 64) (bvudiv aligned_width (_ bv8 64))))) ; size_t low_size4 = u128_branch ? 0 : low_size3 > sizeof(uint64_t) ? sizeof(uint64_t) : low_size3; (declare-fun low_size4 () (_ BitVec 64)) (assert (= low_size4 (ite u128_branch (_ bv0 64) (ite (bvugt low_size3 (_ bv8 64)) (_ bv8 64) low_size3)))) ; u128_branch ? (void) : memcpy(&low3, h_base, low_size4); (declare-fun low3 () (_ BitVec 64)) (assert (= low3 (ite u128_branch (_ bv0 64) (bvor (ite (bvugt low_size4 (_ bv0 64)) (bvshl ((_ zero_extend 56) ((_ extract 7 0) h_base)) (_ bv0 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv1 64)) (bvshl ((_ zero_extend 56) ((_ extract 15 8) h_base)) (_ bv8 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv2 64)) (bvshl ((_ zero_extend 56) ((_ extract 23 16) h_base)) (_ bv16 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv3 64)) (bvshl ((_ zero_extend 56) ((_ extract 31 24) h_base)) (_ bv24 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv4 64)) (bvshl ((_ zero_extend 56) ((_ extract 39 32) h_base)) (_ bv32 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv5 64)) (bvshl ((_ zero_extend 56) ((_ extract 47 40) h_base)) (_ bv40 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv6 64)) (bvshl ((_ zero_extend 56) ((_ extract 55 48) h_base)) (_ bv48 64)) (_ bv0 64)) (ite (bvugt low_size4 (_ bv7 64)) (bvshl ((_ zero_extend 56) ((_ extract 63 56) h_base)) (_ bv56 64)) (_ bv0 64)))))) ; uint64_t or_mask4 = u128_branch ? 0 : ((uint64_t)v) << h_offset; (declare-fun or_mask4 () (_ BitVec 64)) (assert (= or_mask4 (ite u128_branch (_ bv0 64) (bvshl ((_ extract 63 0) v) h_offset)))) ; uint64_t or_mask5 = u128_branch ? 0 : low_size4 < sizeof(low3) ? or_mask4 & ((UINT64_C(1) << (low_size4 * 8)) - 1) : or_mask4; (declare-fun or_mask5 () (_ BitVec 64)) (assert (= or_mask5 (ite u128_branch (_ bv0 64) (ite (bvult low_size4 (_ bv8 64)) (bvand or_mask4 (bvsub (bvshl (_ bv1 64) (bvmul low_size4 (_ bv8 64))) (_ bv1 64))) or_mask4)))) ; uint64_t and_mask4 = u128_branch ? 0 : (UINT64_C(1) << h_offset) - 1; (declare-fun and_mask4 () (_ BitVec 64)) (assert (= and_mask4 (ite u128_branch (_ bv0 64) (bvsub (bvshl (_ bv1 64) h_offset) (_ bv1 64))))) ; size_t high_bits2 = u128_branch ? 0 : h_wdith + h_offset < sizeof(low3) * 8 ? aligned_width - h_offset - h_width : 0; (declare-fun high_bits2 () (_ BitVec 64)) (assert (= high_bits2 (ite u128_branch (_ bv0 64) (ite (bvult (bvadd h_width h_offset) (bvmul (_ bv8 64) (_ bv8 64))) (bvsub (bvsub aligned_width h_offset) h_width) (_ bv0 64))))) ; uint64_t and_mask5 = u128_branch ? 0 : h_width + h_offset < sizeof(low3) * 8 ? and_mask4 | (((UINT64_C(1) << high_bits2) - 1) << (low_size4 * 8 - high_bits2)) : and_mask4; (declare-fun and_mask5 () (_ BitVec 64)) (assert (= and_mask5 (ite u128_branch (_ bv0 64) (ite (bvult (bvadd h_width h_offset) (bvmul (_ bv8 64) (_ bv8 64))) (bvor and_mask4 (bvshl (bvsub (bvshl (_ bv1 64) high_bits2) (_ bv1 64)) (bvsub (bvmul low_size4 (_ bv8 64)) high_bits2))) and_mask4)))) ; uint64_t low4 = u128_branch ? 0 : (low3 & and_mask5) | or_mask5; (declare-fun low4 () (_ BitVec 64)) (assert (= low4 (ite u128_branch (_ bv0 64) (bvor (bvand low3 and_mask5) or_mask5)))) ; u128_branch ? (void) : memcpy(h_base4, &low4, low_size4); (declare-fun h_base4 () (_ BitVec 136)) (assert (= h_base4 (ite u128_branch (_ bv0 136) (bvor (ite (bvugt low_size4 (_ bv0 64)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) low4)) (_ bv0 136)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) h_base)) (_ bv0 136))) (ite (bvugt low_size4 (_ bv1 64)) (bvshl ((_ zero_extend 128) ((_ extract 15 8) low4)) (_ bv8 136)) (bvshl ((_ zero_extend 128) ((_ extract 15 8) h_base)) (_ bv8 136))) (ite (bvugt low_size4 (_ bv2 64)) (bvshl ((_ zero_extend 128) ((_ extract 23 16) low4)) (_ bv16 136)) (bvshl ((_ zero_extend 128) ((_ extract 23 16) h_base)) (_ bv16 136))) (ite (bvugt low_size4 (_ bv3 64)) (bvshl ((_ zero_extend 128) ((_ extract 31 24) low4)) (_ bv24 136)) (bvshl ((_ zero_extend 128) ((_ extract 31 24) h_base)) (_ bv24 136))) (ite (bvugt low_size4 (_ bv4 64)) (bvshl ((_ zero_extend 128) ((_ extract 39 32) low4)) (_ bv32 136)) (bvshl ((_ zero_extend 128) ((_ extract 39 32) h_base)) (_ bv32 136))) (ite (bvugt low_size4 (_ bv5 64)) (bvshl ((_ zero_extend 128) ((_ extract 47 40) low4)) (_ bv40 136)) (bvshl ((_ zero_extend 128) ((_ extract 47 40) h_base)) (_ bv40 136))) (ite (bvugt low_size4 (_ bv6 64)) (bvshl ((_ zero_extend 128) ((_ extract 55 48) low4)) (_ bv48 136)) (bvshl ((_ zero_extend 128) ((_ extract 55 48) h_base)) (_ bv48 136))) (ite (bvugt low_size4 (_ bv7 64)) (bvshl ((_ zero_extend 128) ((_ extract 63 56) low4)) (_ bv56 136)) (bvshl ((_ zero_extend 128) ((_ extract 63 56) h_base)) (_ bv56 136))) (bvshl ((_ zero_extend 64) ((_ extract 135 64) h_base)) (_ bv64 136)))))) ; size_t high_size2 = u128_branch ? 0 : aligned_width / 8 - low_size4; (declare-fun high_size2 () (_ BitVec 64)) (assert (= high_size2 (ite u128_branch (_ bv0 64) (bvsub (bvudiv aligned_width (_ bv8 64)) low_size4)))) ; u128_branch ? (void) : high_size2 != 0 ? memcpy(&high3, h_base4 + sizeof(low4), high_size2) : (void); (declare-fun high3 () (_ BitVec 64)) (assert (= high3 (ite u128_branch (_ bv0 64) (ite (not (= high_size2 (_ bv0 64))) (bvor (ite (bvugt high_size2 (_ bv0 64)) (bvshl ((_ zero_extend 56) ((_ extract 7 0) (bvlshr h_base4 (_ bv64 136)))) (_ bv0 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv1 64)) (bvshl ((_ zero_extend 56) ((_ extract 15 8) (bvlshr h_base4 (_ bv64 136)))) (_ bv8 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv2 64)) (bvshl ((_ zero_extend 56) ((_ extract 23 16) (bvlshr h_base4 (_ bv64 136)))) (_ bv16 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv3 64)) (bvshl ((_ zero_extend 56) ((_ extract 31 24) (bvlshr h_base4 (_ bv64 136)))) (_ bv24 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv4 64)) (bvshl ((_ zero_extend 56) ((_ extract 39 32) (bvlshr h_base4 (_ bv64 136)))) (_ bv32 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv5 64)) (bvshl ((_ zero_extend 56) ((_ extract 47 40) (bvlshr h_base4 (_ bv64 136)))) (_ bv40 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv6 64)) (bvshl ((_ zero_extend 56) ((_ extract 55 48) (bvlshr h_base4 (_ bv64 136)))) (_ bv48 64)) (_ bv0 64)) (ite (bvugt high_size2 (_ bv7 64)) (bvshl ((_ zero_extend 56) ((_ extract 63 56) (bvlshr h_base4 (_ bv64 136)))) (_ bv56 64)) (_ bv0 64))) (_ bv0 64))))) ; uint64_t or_mask6 = u128_branch ? 0 : high_size2 != 0 ? ((uint64_t)v) >> (sizeof(low4) * 8 - h_offset) : 0; (declare-fun or_mask6 () (_ BitVec 64)) (assert (= or_mask6 (ite u128_branch (_ bv0 64) (ite (not (= high_size2 (_ bv0 64))) (bvlshr ((_ extract 63 0) v) (bvsub (bvmul (_ bv8 64) (_ bv8 64)) h_offset)) (_ bv0 64))))) ; uint64_t and_mask6 = u128_branch ? 0 : high_size2 != 0 ? ~((UINT64_C(1) << (h_width + h_offset - sizeof(low4) * 8)) - 1) : 0; (declare-fun and_mask6 () (_ BitVec 64)) (assert (= and_mask6 (ite u128_branch (_ bv0 64) (ite (not (= high_size2 (_ bv0 64))) (bvnot (bvsub (bvshl (_ bv1 64) (bvsub (bvadd h_width h_offset) (_ bv64 64))) (_ bv1 64))) (_ bv0 64))))) ; uint64_t high4 = u128_branch ? 0 : high_size2 != 0 ? (high3 & and_mask6) | or_mask6 : 0; (declare-fun high4 () (_ BitVec 64)) (assert (= high4 (ite u128_branch (_ bv0 64) (ite (not (= high_size2 (_ bv0 64))) (bvor (bvand high3 and_mask6) or_mask6) (_ bv0 64))))) ; u128_branch ? (void) : high_size2 != 0 ? memcpy(h_base5 + sizeof(low4), &high4, high_size2) : (void); (declare-fun h_base5 () (_ BitVec 136)) (assert (= h_base5 (ite u128_branch (_ bv0 136) (ite (not (= high_size2 (_ bv0 64))) (bvor ((_ zero_extend 72) ((_ extract 63 0) h_base4)) (ite (bvugt high_size2 (_ bv0 64)) (bvshl ((_ zero_extend 128) ((_ extract 7 0) high4)) (_ bv64 136)) (bvshl ((_ zero_extend 128) ((_ extract 71 64) h_base4)) (_ bv64 136))) (ite (bvugt high_size2 (_ bv1 64)) (bvshl ((_ zero_extend 128) ((_ extract 15 8) high4)) (_ bv72 136)) (bvshl ((_ zero_extend 128) ((_ extract 79 72) h_base4)) (_ bv72 136))) (ite (bvugt high_size2 (_ bv2 64)) (bvshl ((_ zero_extend 128) ((_ extract 23 16) high4)) (_ bv80 136)) (bvshl ((_ zero_extend 128) ((_ extract 87 80) h_base4)) (_ bv80 136))) (ite (bvugt high_size2 (_ bv3 64)) (bvshl ((_ zero_extend 128) ((_ extract 31 24) high4)) (_ bv88 136)) (bvshl ((_ zero_extend 128) ((_ extract 95 88) h_base4)) (_ bv88 136))) (ite (bvugt high_size2 (_ bv4 64)) (bvshl ((_ zero_extend 128) ((_ extract 39 32) high4)) (_ bv96 136)) (bvshl ((_ zero_extend 128) ((_ extract 103 96) h_base4)) (_ bv96 136))) (ite (bvugt high_size2 (_ bv5 64)) (bvshl ((_ zero_extend 128) ((_ extract 47 40) high4)) (_ bv104 136)) (bvshl ((_ zero_extend 128) ((_ extract 111 104) h_base4)) (_ bv104 136))) (ite (bvugt high_size2 (_ bv6 64)) (bvshl ((_ zero_extend 128) ((_ extract 55 48) high4)) (_ bv112 136)) (bvshl ((_ zero_extend 128) ((_ extract 119 112) h_base4)) (_ bv112 136))) (ite (bvugt high_size2 (_ bv7 64)) (bvshl ((_ zero_extend 128) ((_ extract 63 56) high4)) (_ bv120 136)) (bvshl ((_ zero_extend 128) ((_ extract 127 120) h_base4)) (_ bv120 136))) (bvshl ((_ zero_extend 128) ((_ extract 135 128) h_base4)) (_ bv128 136))) h_base4)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; We can now prove the specification we described at the top of this file. (assert (not (and ; on the 128-bit branch, the input value is correctly written to the store (or (not u128_branch) (= (bvlshr (bvand h_base3 (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) (bvadd h_width h_offset))) (_ bv1 136))) ((_ zero_extend 72) h_offset)) (bvand ((_ zero_extend 8) v) (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_width)) (_ bv1 136))))) ; on the 128-bit branch, all bits below the store range are unaffected (or (not u128_branch) (= h_offset (_ bv0 64)) (= (bvand h_base (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_offset)) (_ bv1 136))) (bvand h_base3 (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_offset)) (_ bv1 136))))) ; on the 128-bit branch, all bits above the store range are unaffected (or (not u128_branch) (= (bvlshr h_base ((_ zero_extend 72) (bvadd h_width h_offset))) (bvlshr h_base3 ((_ zero_extend 72) (bvadd h_width h_offset))))) ; on the 64-bit branch with a width ≤ 64, the input value is correctly ; written to the store (or u128_branch (bvugt h_width (_ bv64 64)) (= (bvlshr (bvand h_base5 (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) (bvadd h_width h_offset))) (_ bv1 136))) ((_ zero_extend 72) h_offset)) (bvand ((_ zero_extend 8) v) (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_width)) (_ bv1 136))))) ; on the 64-bit branch with a width ≤ 64, all bits below the store range are ; unaffected (or u128_branch (= h_offset (_ bv0 64)) (bvugt h_width (_ bv64 64)) (= (bvand h_base (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_offset)) (_ bv1 136))) (bvand h_base5 (bvsub (bvshl (_ bv1 136) ((_ zero_extend 72) h_offset)) (_ bv1 136))))) ; on the 64-bit branch with a width ≤ 64, all bits above the store range are ; unaffected (or u128_branch (bvugt h_width (_ bv64 64)) (= (bvlshr h_base ((_ zero_extend 72) (bvadd h_width h_offset))) (bvlshr h_base5 ((_ zero_extend 72) (bvadd h_width h_offset))))) ))) (check-sat) rumur-2020.02.17/misc/xxd.py000077500000000000000000000020301362265074000153720ustar00rootroot00000000000000#!/usr/bin/env python3 import argparse import re import sys def main(args: [str]) -> int: # parse command line arguments parser = argparse.ArgumentParser( description='convert file contents to a C++ string') parser.add_argument('input', type=argparse.FileType('rb'), help='input file') parser.add_argument('output', type=argparse.FileType('wt'), help='output file') options = parser.parse_args(args[1:]) array = re.sub(r'[^\w\d]', '_', options.input.name) size = f'{array}_len' options.output.write( '#include \n' '\n' f'extern const unsigned char {array}[] = {{') index = 0 while True: c = options.input.read(1) if c == b'': break if index % 12 == 0: options.output.write('\n ') options.output.write(f' 0x{int.from_bytes(c, byteorder="little"):02x},') index += 1 options.output.write( '\n' '};\n' f'extern const size_t {size} = sizeof({array}) / sizeof({array}[0]);\n') return 0 if __name__ == '__main__': sys.exit(main(sys.argv)) rumur-2020.02.17/murphi2xml/000077500000000000000000000000001362265074000153735ustar00rootroot00000000000000rumur-2020.02.17/murphi2xml/CMakeLists.txt000066400000000000000000000017441362265074000201410ustar00rootroot00000000000000add_custom_command( OUTPUT manpage.cc COMMAND ../misc/xxd.py doc/murphi2xml.1 ${CMAKE_CURRENT_BINARY_DIR}/manpage.cc MAIN_DEPENDENCY doc/murphi2xml.1 DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_executable(murphi2xml ${CMAKE_CURRENT_BINARY_DIR}/manpage.cc ../common/help.cc src/main.cc src/XMLPrinter.cc) target_include_directories(murphi2xml PRIVATE src ${CMAKE_CURRENT_BINARY_DIR}/../librumur) target_link_libraries(murphi2xml librumur) # Compress manpages add_custom_target(man-murphi2xml ALL DEPENDS murphi2xml.1.gz) add_custom_command( OUTPUT murphi2xml.1.gz COMMAND gzip -9 --no-name --to-stdout doc/murphi2xml.1 >"${CMAKE_CURRENT_BINARY_DIR}/murphi2xml.1.gz" MAIN_DEPENDENCY doc/murphi2xml.1 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) install(TARGETS murphi2xml RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/murphi2xml.1.gz DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) rumur-2020.02.17/murphi2xml/doc/000077500000000000000000000000001362265074000161405ustar00rootroot00000000000000rumur-2020.02.17/murphi2xml/doc/murphi2xml.1000066400000000000000000000013621362265074000203330ustar00rootroot00000000000000.TH MURPHI2XML 1 .SH NAME murphi2xml \- Print the abstract syntax tree of a parsed Murphi model .SH SYNOPSIS .B \fBmurphi2xml\fR [\fB--output\fR \fIFILE\fR | \fB-o\fR \fIFILE\fR] \fIFILE\fR .SH DESCRIPTION The utility \fBmurphi2xml\fR is bundled with the model checker Rumur and can be used to translate a Murphi model into its abstract syntax tree in an XML format. See .BR rumur(1) For more information about Rumur or Murphi. .SH OPTIONS \fB--help\fR or \fB-?\fR .RS Display usage information. .RE .PP \fB--output\fR \fIFILE\fR or \fB-o\fR \fIFILE\fR .RS Set the path to write the output XML to. If this argument is omitted, output is written to standard out. .RE .PP \fB--version\fR .RS Display version information and exit. .RE .SH SEE ALSO rumur(1) rumur-2020.02.17/murphi2xml/src/000077500000000000000000000000001362265074000161625ustar00rootroot00000000000000rumur-2020.02.17/murphi2xml/src/XMLPrinter.cc000066400000000000000000000510641362265074000205030ustar00rootroot00000000000000#include #include #include #include #include #include #include "XMLPrinter.h" using namespace rumur; static std::string xml_escape(char c) { switch (c) { case '"' : return """; case '\'': return "'"; case '<' : return "<"; case '>' : return ">"; case '&' : return "&"; /* XXX: Form feed is apparently not a valid character to use in XML 1.0, * encoded or otherwise. However, some legacy models use this. To cope with * it, we just translate it to a single space. */ case 12 : return " "; default : return std::string(1, c); } } static std::string xml_escape(const std::string &s) { std::string escaped; for (char c : s) escaped += xml_escape(c); return escaped; } XMLPrinter::XMLPrinter(const std::string &in_filename, std::istream &in_, std::ostream &o_): in(in_), o(o_) { // Write out XML version header o << "\n"; o << ""; } void XMLPrinter::visit_add(const Add &n) { visit_bexpr("add", n); } void XMLPrinter::visit_aliasdecl(const AliasDecl &n) { sync_to(n); o << ""; sync_to(*n.value); o << ""; dispatch(*n.value); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_aliasrule(const AliasRule &n) { sync_to(n); o << ""; if (!n.aliases.empty()) { sync_to(*n.aliases[0]); o << ""; for (auto &a : n.aliases) { sync_to(*a); dispatch(*a); } o << ""; } if (!n.rules.empty()) { sync_to(*n.rules[0]); o << ""; for (auto &r : n.rules) { sync_to(*r); dispatch(*r); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_aliasstmt(const AliasStmt &n) { sync_to(n); o << ""; if (!n.aliases.empty()) { sync_to(*n.aliases[0]); o << ""; for (auto &a : n.aliases) { sync_to(*a); dispatch(*a); } o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_and(const And &n) { visit_bexpr("and", n); } void XMLPrinter::visit_array(const Array &n) { sync_to(n); o << ""; sync_to(*n.index_type); o << ""; dispatch(*n.index_type); o << ""; sync_to(*n.element_type); o << ""; dispatch(*n.element_type); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_assignment(const Assignment &n) { sync_to(n); o << ""; dispatch(*n.lhs); o << ""; sync_to(*n.rhs); o << ""; dispatch(*n.rhs); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_clear(const Clear &n) { sync_to(n); o << ""; sync_to(*n.rhs); dispatch(*n.rhs); sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_constdecl(const ConstDecl &n) { sync_to(n); o << ""; sync_to(*n.value); o << ""; dispatch(*n.value); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_div(const Div &n) { visit_bexpr("div", n); } void XMLPrinter::visit_element(const Element &n) { sync_to(n); o << ""; sync_to(*n.array); o << ""; dispatch(*n.array); o << ""; sync_to(*n.index); o << ""; dispatch(*n.index); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_enum(const Enum &n) { sync_to(n); o << ""; for (const std::pair &m : n.members) { sync_to(m.second.begin); o << ""; sync_to(m.second.end); o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_eq(const Eq &n) { visit_bexpr("eq", n); } void XMLPrinter::visit_errorstmt(const ErrorStmt &n) { sync_to(n); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_exists(const Exists &n) { sync_to(n); o << ""; sync_to(n.quantifier); o << ""; dispatch(n.quantifier); o << ""; sync_to(*n.expr); o << ""; dispatch(*n.expr); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_exprid(const ExprID &n) { sync_to(n); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_field(const Field &n) { sync_to(n); o << ""; sync_to(*n.record); o << ""; dispatch(*n.record); o << ""; /* FIXME: We don't have location information for the field itself, so we just * dump the entire remaining text of this node here. Does this produce * inaccurate output? */ sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_for(const For &n) { sync_to(n); o << ""; sync_to(n.quantifier); dispatch(n.quantifier); if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_forall(const Forall &n) { sync_to(n); o << ""; sync_to(n.quantifier); o << ""; dispatch(n.quantifier); o << ""; sync_to(*n.expr); o << ""; dispatch(*n.expr); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_function(const Function &n) { sync_to(n); o << ""; if (!n.parameters.empty()) { sync_to(*n.parameters[0]); o << ""; for (auto &p : n.parameters) { sync_to(*p); dispatch(*p); } o << ""; } if (n.return_type != nullptr) { sync_to(*n.return_type); o << ""; dispatch(*n.return_type); o << ""; } if (!n.decls.empty()) { sync_to(*n.decls[0]); o << ""; for (auto &d : n.decls) { sync_to(*d); dispatch(*d); } o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_functioncall(const FunctionCall &n) { sync_to(n); /* We deliberately list the called function by name as an attribute, rather * than emitting the function itself as a child of this node because morally * this is just a reference to a previously defined function. */ o << ""; for (auto &a : n.arguments) { sync_to(*a); o << ""; dispatch(*a); o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_geq(const Geq &n) { visit_bexpr("geq", n); } void XMLPrinter::visit_gt(const Gt &n) { visit_bexpr("gt", n); } void XMLPrinter::visit_if(const If &n) { sync_to(n); o << ""; for (const IfClause &c : n.clauses) { sync_to(c); dispatch(c); } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_ifclause(const IfClause &n) { sync_to(n); o << ""; if (n.condition != nullptr) { sync_to(*n.condition); o << ""; dispatch(*n.condition); o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_implication(const Implication &n) { visit_bexpr("implication", n); } void XMLPrinter::visit_isundefined(const IsUndefined &n) { sync_to(n); o << ""; sync_to(*n.expr); dispatch(*n.expr); sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_leq(const Leq &n) { visit_bexpr("leq", n); } void XMLPrinter::visit_lt(const Lt &n) { visit_bexpr("lt", n); } void XMLPrinter::visit_mod(const Mod &n) { visit_bexpr("mod", n); } void XMLPrinter::visit_model(const Model &n) { o << ""; if (!n.decls.empty()) { sync_to(*n.decls[0]); o << ""; for (auto &d : n.decls) { sync_to(*d); dispatch(*d); } o << ""; } if (!n.functions.empty()) { sync_to(*n.functions[0]); o << ""; for (auto f : n.functions) { sync_to(*f); dispatch(*f); } o << ""; } if (!n.rules.empty()) { sync_to(*n.rules[0]); o << ""; for (auto &r : n.rules) { sync_to(*r); dispatch(*r); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_mul(const Mul &n) { visit_bexpr("mul", n); } void XMLPrinter::visit_negative(const Negative &n) { visit_uexpr("negative", n); } void XMLPrinter::visit_neq(const Neq &n) { visit_bexpr("neq", n); } void XMLPrinter::visit_not(const Not &n) { visit_uexpr("not", n); } void XMLPrinter::visit_number(const Number &n) { sync_to(n); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_or(const Or &n) { visit_bexpr("or", n); } void XMLPrinter::visit_procedurecall(const ProcedureCall &n) { sync_to(n); o << ""; sync_to(n.call); dispatch(n.call); sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_property(const Property &n) { sync_to(n); o << ""; sync_to(*n.expr); o << ""; dispatch(*n.expr); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_propertyrule(const PropertyRule &n) { sync_to(n); o << ""; if (!n.quantifiers.empty()) { sync_to(n.quantifiers[0]); o << ""; for (const Quantifier &q : n.quantifiers) { sync_to(q); dispatch(q); } o << ""; } sync_to(n.property); dispatch(n.property); sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_propertystmt(const PropertyStmt &n) { sync_to(n); o << ""; sync_to(n.property); dispatch(n.property); sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_put(const Put &n) { sync_to(n); o << ""; if (n.expr != nullptr) { sync_to(*n.expr); dispatch(*n.expr); } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_quantifier(const Quantifier &n) { sync_to(n); o << ""; if (n.type != nullptr) { sync_to(*n.type); o << ""; dispatch(*n.type); o << ""; } if (n.from != nullptr) { sync_to(*n.from); o << ""; dispatch(*n.from); o << ""; } if (n.to != nullptr) { sync_to(*n.to); o << ""; dispatch(*n.to); o << ""; } if (n.step != nullptr) { sync_to(*n.step); o << ""; dispatch(*n.step); o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_range(const Range &n) { sync_to(n); o << ""; sync_to(*n.min); o << ""; dispatch(*n.min); o << ""; sync_to(*n.max); o << ""; dispatch(*n.max); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_record(const Record &n) { sync_to(n); o << ""; for (auto &f : n.fields) { sync_to(*f); dispatch(*f); } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_return(const Return &n) { sync_to(n); o << ""; if (n.expr != nullptr) { sync_to(*n.expr); dispatch(*n.expr); } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_ruleset(const Ruleset &n) { sync_to(n); o << ""; if (!n.quantifiers.empty()) { sync_to(n.quantifiers[0]); o << ""; for (const Quantifier &q : n.quantifiers) { sync_to(q); dispatch(q); } o << ""; } if (!n.rules.empty()) { sync_to(*n.rules[0]); o << ""; for (auto &r : n.rules) { sync_to(*r); dispatch(*r); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_scalarset(const Scalarset &n) { sync_to(n); o << ""; sync_to(*n.bound); o << ""; dispatch(*n.bound); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_simplerule(const SimpleRule &n) { sync_to(n); o << ""; if (!n.quantifiers.empty()) { sync_to(n.quantifiers[0]); o << ""; for (const Quantifier &q : n.quantifiers) { sync_to(q); dispatch(q); } o << ""; } if (n.guard != nullptr) { sync_to(*n.guard); o << ""; dispatch(*n.guard); o << ""; } if (!n.decls.empty()) { sync_to(*n.decls[0]); o << ""; for (auto &d : n.decls) { sync_to(*d); dispatch(*d); } o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_startstate(const StartState &n) { sync_to(n); o << ""; if (!n.quantifiers.empty()) { sync_to(n.quantifiers[0]); o << ""; for (const Quantifier &q : n.quantifiers) { sync_to(q); dispatch(q); } o << ""; } if (!n.decls.empty()) { sync_to(*n.decls[0]); o << ""; for (auto &d : n.decls) { sync_to(*d); dispatch(*d); } o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_sub(const Sub &n) { visit_bexpr("sub", n); } void XMLPrinter::visit_switch(const Switch &n) { sync_to(n); o << ""; sync_to(*n.expr); o << ""; dispatch(*n.expr); o << ""; if (!n.cases.empty()) { sync_to(n.cases[0]); o << ""; for (const SwitchCase &c : n.cases) { sync_to(c); dispatch(c); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_switchcase(const SwitchCase &n) { sync_to(n); o << ""; if (!n.matches.empty()) { sync_to(*n.matches[0]); o << ""; for (auto &m : n.matches) { sync_to(*m); dispatch(*m); } o << ""; } if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_ternary(const Ternary &n) { sync_to(n); o << ""; sync_to(*n.cond); o << ""; dispatch(*n.cond); o << ""; sync_to(*n.lhs); o << ""; dispatch(*n.lhs); o << ""; sync_to(*n.rhs); o << ""; dispatch(*n.rhs); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_typedecl(const TypeDecl &n) { sync_to(n); o << ""; sync_to(*n.value); o << ""; dispatch(*n.value); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_typeexprid(const TypeExprID &n) { sync_to(n); o << ""; /* We deliberately omit n.referent because this is a result of symbol * resolution and not morally a child of this node. */ sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_undefine(const Undefine &n) { sync_to(n); o << ""; sync_to(*n.rhs); dispatch(*n.rhs); sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_vardecl(const VarDecl &n) { sync_to(n); o << ""; sync_to(*n.type); o << ""; dispatch(*n.type); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_while(const While &n) { sync_to(n); o << ""; sync_to(*n.condition); o << ""; dispatch(*n.condition); o << ""; if (!n.body.empty()) { sync_to(*n.body[0]); o << ""; for (auto &s : n.body) { sync_to(*s); dispatch(*s); } o << ""; } sync_to(n.loc.end); o << ""; } XMLPrinter::~XMLPrinter() { sync_to(); o << "\n"; o.flush(); } void XMLPrinter::sync_to(const Node &n) { sync_to(n.loc.begin); } void XMLPrinter::sync_to(const position &pos) { // the type of position.line and position.column changes across Bison // releases, so avoid some -Wsign-compare warnings by casting them in advance auto pos_line = static_cast(pos.line); auto pos_col = static_cast(pos.column); while (in.good() && (line < pos_line || (line == pos_line && column < pos_col))) { int c = in.get(); if (c == EOF) { break; } o << xml_escape(static_cast(c)); if (c == '\n') { line++; column = 1; } else { column++; } } } void XMLPrinter::add_location(const Node &n) { o << "first_line=\"" << n.loc.begin.line << "\" first_column=\"" << n.loc.begin.column << "\" last_line=\"" << n.loc.end.line << "\" last_column=\"" << n.loc.end.column << "\""; } void XMLPrinter::visit_bexpr(const std::string &tag, const BinaryExpr &n) { sync_to(n); o << "<" << tag << " "; add_location(n); o << ">"; sync_to(*n.lhs); o << ""; dispatch(*n.lhs); o << ""; sync_to(*n.rhs); o << ""; dispatch(*n.rhs); o << ""; sync_to(n.loc.end); o << ""; } void XMLPrinter::visit_uexpr(const std::string &tag, const UnaryExpr &n) { sync_to(n); o << "<" << tag << " "; add_location(n); o << ">"; sync_to(*n.rhs); o << ""; dispatch(*n.rhs); o << ""; sync_to(n.loc.end); o << ""; } rumur-2020.02.17/murphi2xml/src/XMLPrinter.h000066400000000000000000000073031362265074000203420ustar00rootroot00000000000000#pragma once #include #include #include #include class XMLPrinter : public rumur::ConstBaseTraversal { private: std::istream ∈ std::ostream &o; unsigned long line = 1; unsigned long column = 1; public: XMLPrinter(const std::string &in_filename, std::istream &in_, std::ostream &o_); void visit_add(const rumur::Add &n) final; void visit_aliasdecl(const rumur::AliasDecl &n) final; void visit_aliasrule(const rumur::AliasRule &n) final; void visit_aliasstmt(const rumur::AliasStmt &n) final; void visit_and(const rumur::And &n) final; void visit_array(const rumur::Array &n) final; void visit_assignment(const rumur::Assignment &n) final; void visit_clear(const rumur::Clear &n) final; void visit_constdecl(const rumur::ConstDecl &n) final; void visit_div(const rumur::Div &n) final; void visit_element(const rumur::Element &n) final; void visit_enum(const rumur::Enum &n) final; void visit_eq(const rumur::Eq &n) final; void visit_errorstmt(const rumur::ErrorStmt &n) final; void visit_exists(const rumur::Exists &n) final; void visit_exprid(const rumur::ExprID &n) final; void visit_field(const rumur::Field &n) final; void visit_for(const rumur::For &n) final; void visit_forall(const rumur::Forall &n) final; void visit_function(const rumur::Function &n) final; void visit_functioncall(const rumur::FunctionCall &n) final; void visit_geq(const rumur::Geq &n) final; void visit_gt(const rumur::Gt &n) final; void visit_if(const rumur::If &n) final; void visit_ifclause(const rumur::IfClause &n) final; void visit_implication(const rumur::Implication &n) final; void visit_isundefined(const rumur::IsUndefined &n) final; void visit_leq(const rumur::Leq &n) final; void visit_lt(const rumur::Lt &n) final; void visit_mod(const rumur::Mod &n) final; void visit_model(const rumur::Model &n) final; void visit_mul(const rumur::Mul &n) final; void visit_negative(const rumur::Negative &n) final; void visit_neq(const rumur::Neq &n) final; void visit_not(const rumur::Not &n) final; void visit_number(const rumur::Number &n) final; void visit_or(const rumur::Or &n) final; void visit_procedurecall(const rumur::ProcedureCall &n) final; void visit_property(const rumur::Property &n) final; void visit_propertyrule(const rumur::PropertyRule &n) final; void visit_propertystmt(const rumur::PropertyStmt &n) final; void visit_put(const rumur::Put &n) final; void visit_quantifier(const rumur::Quantifier &n) final; void visit_range(const rumur::Range &n) final; void visit_record(const rumur::Record &n) final; void visit_return(const rumur::Return &n) final; void visit_ruleset(const rumur::Ruleset &n) final; void visit_scalarset(const rumur::Scalarset &n) final; void visit_simplerule(const rumur::SimpleRule &n) final; void visit_startstate(const rumur::StartState &n) final; void visit_sub(const rumur::Sub &n) final; void visit_switch(const rumur::Switch &n) final; void visit_switchcase(const rumur::SwitchCase &n) final; void visit_ternary(const rumur::Ternary &n) final; void visit_typedecl(const rumur::TypeDecl &n) final; void visit_typeexprid(const rumur::TypeExprID &n) final; void visit_undefine(const rumur::Undefine &n) final; void visit_vardecl(const rumur::VarDecl &n) final; void visit_while(const rumur::While &n) final; virtual ~XMLPrinter(); private: void add_location(const rumur::Node &n); void visit_bexpr(const std::string &tag, const rumur::BinaryExpr &n); void visit_uexpr(const std::string &tag, const rumur::UnaryExpr &n); void sync_to(const rumur::Node &n); void sync_to(const rumur::position &pos = rumur::position(nullptr, UINT_MAX, UINT_MAX)); }; rumur-2020.02.17/murphi2xml/src/main.cc000066400000000000000000000062671362265074000174300ustar00rootroot00000000000000#include #include #include #include #include "../../common/help.h" #include #include #include "resources.h" #include #include #include #include #include #include "XMLPrinter.h" static std::string in_filename = ""; static std::shared_ptr in; static std::shared_ptr in_replay; static std::shared_ptr out; // buffer the contents of stdin so we can read it twice static void buffer_stdin(void) { // read in all of stdin std::ostringstream buf; buf << std::cin.rdbuf(); buf.flush(); // put this into two buffers we can read from in = std::make_shared(buf.str()); in_replay = std::make_shared(buf.str()); } static void parse_args(int argc, char **argv) { for (;;) { static struct option options[] = { { "help", no_argument, 0, '?' }, { "output", required_argument, 0, 'o' }, { "version", no_argument, 0, 128 }, { 0, 0, 0, 0 }, }; int option_index = 0; int c = getopt_long(argc, argv, "o:", options, &option_index); if (c == -1) break; switch (c) { case '?': help(doc_murphi2xml_1, doc_murphi2xml_1_len); exit(EXIT_SUCCESS); case 'o': { auto o = std::make_shared(optarg); if (!o->is_open()) { std::cerr << "failed to open " << optarg << "\n"; exit(EXIT_FAILURE); } out = o; break; } case 128: // --version std::cout << "Rumur version " << rumur::get_version() << "\n"; exit(EXIT_SUCCESS); default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); } } if (optind == argc - 1) { struct stat buf; if (stat(argv[optind], &buf) < 0) { std::cerr << "failed to open " << argv[optind] << ": " << strerror(errno) << "\n"; exit(EXIT_FAILURE); } if (S_ISDIR(buf.st_mode)) { std::cerr << "failed to open " << argv[optind] << ": this is a directory\n"; exit(EXIT_FAILURE); } in_filename = argv[optind]; auto i = std::make_shared(in_filename); if (!i->is_open()) { std::cerr << "failed to open " << in_filename << "\n"; exit(EXIT_FAILURE); } in = i; // open the input again that we need for replay during XML output auto i2 = std::make_shared(in_filename); if (!i2->is_open()) { std::cerr << "failed to open " << in_filename << "\n"; exit(EXIT_FAILURE); } in_replay = i2; } else { // we are going to read data from stdin buffer_stdin(); } } int main(int argc, char **argv) { // Parse command line options parse_args(argc, argv); assert(in != nullptr); // Parse input model rumur::Ptr m; try { m = rumur::parse(*in); resolve_symbols(*m); validate(*m); } catch (rumur::Error &e) { std::cerr << e.loc << ":" << e.what() << "\n"; return EXIT_FAILURE; } assert(m != nullptr); { XMLPrinter p(in_filename, *in_replay, out == nullptr ? std::cout : *out); p.dispatch(*m); } return EXIT_SUCCESS; } rumur-2020.02.17/murphi2xml/src/resources.h000066400000000000000000000001731362265074000203460ustar00rootroot00000000000000#pragma once #include extern const unsigned char doc_murphi2xml_1[]; extern const size_t doc_murphi2xml_1_len; rumur-2020.02.17/rumur/000077500000000000000000000000001362265074000144365ustar00rootroot00000000000000rumur-2020.02.17/rumur/CMakeLists.txt000066400000000000000000000054251362265074000172040ustar00rootroot00000000000000# Define resources. add_custom_command( OUTPUT resources_manpage.cc COMMAND ../misc/xxd.py doc/rumur.1 ${CMAKE_CURRENT_BINARY_DIR}/resources_manpage.cc MAIN_DEPENDENCY doc/rumur.1 DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_command( OUTPUT resources_includes.cc COMMAND ../misc/xxd.py resources/includes.c ${CMAKE_CURRENT_BINARY_DIR}/resources_includes.cc MAIN_DEPENDENCY resources/includes.c DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_command( OUTPUT resources_header.cc COMMAND ../misc/xxd.py resources/header.c ${CMAKE_CURRENT_BINARY_DIR}/resources_header.cc MAIN_DEPENDENCY resources/header.c DEPENDS ../misc/xxd.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_executable(rumur ${CMAKE_CURRENT_BINARY_DIR}/resources_includes.cc ${CMAKE_CURRENT_BINARY_DIR}/resources_header.cc ${CMAKE_CURRENT_BINARY_DIR}/resources_manpage.cc ../common/help.cc src/assume-statements-count.cc src/environ.cc src/generate-allocations.cc src/generate-cover-array.cc src/generate-decl.cc src/generate-expr.cc src/generate-function.cc src/generate-model.cc src/generate-print.cc src/generate-property.cc src/generate-quantifier.cc src/generate-stmt.cc src/has-start-state.cc src/log.cc src/main.cc src/max-simple-width.cc src/options.cc src/output.cc src/process.cc src/smt/define-enum-members.cc src/smt/define-records.cc src/smt/logic.cc src/smt/simplify.cc src/smt/solver.cc src/smt/translate.cc src/smt/typeexpr-to-smt.cc src/symmetry-reduction.cc src/utils.cc src/ValueType.cc) target_include_directories(rumur PRIVATE src # FIXME: This is a hack to include generated headers from the library. We # really want to be able to stage these somewhere and talk about them here # as if they were just a regular, static exported header. ${CMAKE_CURRENT_BINARY_DIR}/../librumur) target_link_libraries(rumur librumur) # Compress manpages add_custom_target(man-rumur ALL DEPENDS rumur.1.gz rumur-run.1.gz) add_custom_command( OUTPUT rumur.1.gz COMMAND gzip -9 --no-name --to-stdout doc/rumur.1 >"${CMAKE_CURRENT_BINARY_DIR}/rumur.1.gz" MAIN_DEPENDENCY doc/rumur.1 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) add_custom_command( OUTPUT rumur-run.1.gz COMMAND gzip -9 --no-name --to-stdout doc/rumur-run.1 >"${CMAKE_CURRENT_BINARY_DIR}/rumur-run.1.gz" MAIN_DEPENDENCY doc/rumur-run.1 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) install(TARGETS rumur RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/src/rumur-run DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/rumur.1.gz ${CMAKE_CURRENT_BINARY_DIR}/rumur-run.1.gz DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) rumur-2020.02.17/rumur/doc/000077500000000000000000000000001362265074000152035ustar00rootroot00000000000000rumur-2020.02.17/rumur/doc/rumur-run.1000066400000000000000000000005711362265074000172440ustar00rootroot00000000000000.TH RUMUR-RUN 1 .SH NAME rumur \- Yet another explicit state model checker .SH SYNOPSIS .B \fBrumur-run\fR \fBoptions...\fR .SH DESCRIPTION As a convenient way to run \fBrumur\fR, the wrapper \fBrumur-run\fR is provided. It runs \fBrumur\fR with the given options, compiles the generated verifier and then runs it. See .BR rumur(1) for available options. .SH SEE ALSO rumur(1) rumur-2020.02.17/rumur/doc/rumur.1000066400000000000000000000331361362265074000164450ustar00rootroot00000000000000.TH RUMUR 1 .SH NAME rumur \- Yet another explicit state model checker .SH SYNOPSIS .B \fBrumur\fR \fBoptions\fR \fB--output\fR \fIFILE\fR [\fIFILE\fR] .SH DESCRIPTION Rumur is a reimplementation of the model checker CMurphi with improved performance and a slightly different feature set. .SH OPTIONS \fB--bound\fR \fISTEPS\fR .RS Set a limit for state space exploration. The verifier will stop checking beyond this depth. A bound of \fB0\fR, the default, indicates unlimited exploration. That is, the verifier will not stop checking until it has expanded all seen states. .RE .PP \fB--colour\fR [\fBauto\fR | \fBoff\fR | \fBon\fR] .RS Enable or disable the use of ANSI colour codes in the verifier's output. The default is \fBauto\fR, to auto-detect based on whether the verifier's stdout is a TTY. .RE .PP \fB--counterexample-trace\fR [\fBdiff\fR | \fBfull\fR | \fBoff\fR] .RS Set how counterexample traces are printed when an error is found during checking. \fBdiff\fR, the default, prints each state showing only the differences from the previous state. \fBfull\fR shows the entire contents of each state. \fBoff\fR disables counterexample trace printing altogether. .RE .PP \fB--deadlock-detection\fR [\fBoff\fR | \fBstuck\fR | \fBstuttering\fR] .RS Enable or disable deadlock detection. Rumur has the ability to generate a verifier that notices when there is no valid transition out of a state and raise an error in this scenario. The possible modes for this check are: .RS .IP \[bu] 2 \fBoff\fR No deadlock checks are performed. .IP \[bu] \fBstuck\fR A deadlock is reached when arriving at a state from which there are no enabled transitions, and an error is signaled in this case. .IP \[bu] \fBstuttering\fR A deadlock is reached in the same circumstances as for the \fBstuck\fR option or additionally if there are enabled transitions but these all result in an identical state. For CMurphi users, this is the scenario that CMurphi considers to be a deadlock. .RE .PP This defaults to \fBstuttering\fR. However, whether such a deadlock actually represents a problem depends on the properties of the system you are modelling. Hence you may want to change deadlock detection. .RE .PP \fB--debug\fR or \fB-d\fR .RS Enable debugging options in the generated verifier. This includes enabling runtime assertions. This will also output debugging messages while generating the verifier. .RE .PP \fB--help\fR .RS Display this information. .RE .PP \fB--max-errors\fR \fICOUNT\fR .RS Number of errors the verifier should report before considering them fatal. By default this is \fI1\fR, that is exit as soon as an error is encountered. However, you may wish to set a higher value to get multiple error traces from a single run. .RE .PP \fB--monopolise\fR .RS Assume that the machine the generated verifier will run on is the current host and that it will be the only process running. This flag causes the verifier to upfront allocate a seen set that will eventually occupy all of memory. That is, it is the same as passing \fB--set-expand-threshold 100\fR and \fB--set-capacity\fR with the total amount of physical memory available on the current machine. .RE .PP \fB--output\fR \fIFILE\fR or \fB-o\fR \fIFILE\fR .RS Set path to write the generated C verifier's code to. .RE .PP \fB--output-format\fR [\fBmachine-readable\fR | \fBhuman-readable\fR] .RS Change the format in which the verifier displays its output. By default, it uses \fBhuman-readable\fR which results in progress output and then a final summary of the result. Using \fBmachine-readable\fR generates output in an XML format suitable for consumption by a following tool in an I/O pipeline. .RE .PP \fB--pack-state\fR [\fBon\fR | \fBoff\fR] .RS Set whether auxiliary state data is compressed in the generated verifier. Compression (\fBon\fR, the default) saves memory at the expense of runtime. That is, by default the verifier will try to minimise memory usage. If your model is small enough to comfortably fit in available memory, you may want to set this to \fBoff\fR to accelerate the checking process. .RE .PP \fB--quiet\fR or \fB-q\fR .RS Don't output any messages while generating the verifier. .RE .PP \fB--sandbox\fR [\fBon\fR | \fBoff\fR] .RS Control whether the generated verifier uses your operating system's sandboxing facilities to limit its own operations. The verifier does not intentionally perform any malicious or dangerous operations, but at the end of the day it is a generated program that you are going to execute. To safeguard against a bug in the code generator, it is recommended to constrain the operations the verifier is allowed to perform if you are using a model you did not write yourself. By default this is \fBoff\fR. .RE .PP \fB--set-capacity\fR \fISIZE\fR or \fB-s\fR \fISIZE\fR .RS The size of the initial set to allocate for storing seen states. This is given in bytes and is interpreted to mean the desired size of the set when it is completely full. That is, the initial allocation performed will be for a number of "state slots" that, when all occupied, will consume this much memory. Default value for this 8MB. .RE .PP \fB--set-expand-threshold\fR \fIPERCENT\fR or \fB-e\fR \fIPERCENT\fR .RS Expand the state set when its occupancy exceeds this percentage. Default is \fI75\fR, valid values are \fI1\fR - \fI100\fR. Setting a value of 100 will result in the set only expanding when completely full. This may sound ideal, but will actually result in a much longer runtime. .RE .PP \fB--symmetry-reduction\fR [\fBoff\fR | \fBheuristic\fR | \fBexhaustive\fR] .RS Enable or disable symmetry reduction. Symmetry reduction is an optimisation that decreases the state space that must be searched by deriving a canonical representation of each state. While two states may not be directly equal, if their canonical representations are the same only one of them need be expanded. To take advantage of this optimisation you must be using named \fBscalarset\fR types. The available options are: .RS .IP \[bu] 2 \fBoff\fR Do not use symmetry reduction. All scalarsets will be treated as if they were range types. .IP \[bu] \fBheuristic\fR Use a symmetry reduction algorithm based on sorting the state data. This is not guaranteed to find a single, canonical representation for each equivalent state, but it is fast and performs reasonably well empirically. Using this option, you may explore more states than you need to, with the advantage that you will process each individual state much faster than with \fBexhaustive\fR. This is the default. .IP \[bu] \fBexhaustive\fR Use a symmetry reduction algorithm based on exhaustive permutation of the state data. This is guaranteed to find a single, canonical representation for each equivalent state, but is typically very slow. Use this if you want to minimise memory usage at the expense of runtime. .RE .RE .PP \fB--threads\fR \fICOUNT\fR or \fB-t\fR \fICOUNT\fR .RS Specify the number of threads the verifier should use. If you do not specify this parameter or pass \fI0\fR, the number of threads will be chosen based on the available hardware threads on the platform on which you generate the model. .RE .PP \fB--trace\fR \fICATEGORY\fR .RS Enable tracing of specific events while checking. This option is for debugging Rumur itself, and lets you generate a verifier that writes events to stderr. Available event categories are: .RS .IP \[bu] 2 \fIhandle_reads\fR Reads from variable handles .IP \[bu] \fIhandle_writes\fR Writes to variable handles .IP \[bu] \fImemory_usage\fR Summary of memory allocation during checking .IP \[bu] \fIqueue\fR Events relating to the pending state queue .IP \[bu] \fIset\fR Events relating to the seen state set .IP \[bu] \fIsymmetry_reduction\fR Events related to the symmetry reduction optimisation .IP \[bu] \fIall\fR Enable all of the above .RE .PP More than one of these can be enabled at once by passing the \fB--trace\fR argument multiple times. Note that enabling tracing will significantly slow the verifier and is only intended for debugging purposes. .RE .PP \fB--value-type\fR \fITYPE\fR .RS Change the C type used to represent scalar values in the generated verifier. Valid values are \fIauto\fR and the C fixed-width types, \fIint8_t\fR, \fIuint8_t\fR, \fIint16_t\fR, \fIuint16_t\fR, \fIint32_t\fR, \fIuint32_t\fR, \fIint64_t\fR, and \fIuint64_t\fR. The type you select is mapped to its fast equivalent (e.g. \fIint_fast8_t\fR) and then used in the verifier. The default is \fIauto\fR that selects the narrowest type that can contain all the scalar types in use in your model. It is possible that your model does some arithmetic that temporarily exceeds the bound of any declared type in your model, in which case you will need to use this option to select a wider type. However, this is not a common case. .RE .PP \fB--verbose\fR or \fB-v\fR .RS Output informational messages while generating the verifier. .RE .PP \fB--version\fR .RS Display version information and exit. .RE .SH SMT OPTIONS If you have a Satisfiability Modulo Theories (SMT) solver installed, Rumur can use it to optimise your model while generating a verifier. This functionality is not enabled by default, but you can use the following options to configure Rumur to find and use your SMT solver. Some examples of solver configuration: .PP .RS # for Z3 with a 5 second timeout .br \fBrumur --smt-path z3 --smt-arg=-smt2 --smt-arg=-in --smt-arg=-t:5000 ...\fR .PP # for CVC4 with a 5 second timeout .br \fBrumur --smt-path cvc4 --smt-prelude "(set-logic AUFLIA)" --smt-arg=--lang=smt2 --smt-arg=--rewrite-divk --smt-arg=--tlimit=5000 ...\fR .RE .PP For other solvers, consult their manpages or documentation to determine what command line parameters they accept. Then use the options described below to instruct Rumur how to use them. Note that Rumur can only use a single SMT solver and specifying the \fB--smt-path\fR option multiple times will only retain the last path given. .PP \fB--smt-arg\fR \fIARG\fR .RS A command line argument to pass to the SMT solver. This option can be given multiple times and arguments are passed in the order listed. E.g. if you specify \fB--smt-arg=--tlimit\fR \fB--smt-arg=5000\fR the solver will be called with the command line arguments \fB--tlimit\fR \fB5000\fR. .RE .PP \fB--smt-bitvectors\fR [\fBoff\fR | \fBon\fR] .RS Select whether simple types (enums, ranges, and scalarsets) are translated to bitvectors or unbounded integers when passed to the solver. By default, unbounded integers are used (\fB--smt-bitvectors off\fR). If you turn this option on, 64-bit vectors are used instead. Whether integers, bitvectors, or both are supported will depend on your solver as well as the SMT logic you are using. .RE .PP \fB--smt-budget\fR \fIMILLISECONDS\fR .RS Total time allotted for running the SMT solver. That is, the time the solver will be allowed to run for over multiple executions. This defaults to \fI30000\fR, 30 seconds. So if the solver runs for 10 seconds the first time it is called, then 5 seconds the second time it is called, then 20 seconds the third time it is called, it will not be called again. Note that Rumur trusts the SMT solver to limit itself to a reasonable timeout per run, so its final run can exceed the budget. You may want to use the \fB--smt-arg\fR option to pass the SMT solver a timeout limit if it supports one. .RE .PP \fB--smt-path\fR \fIPATH\fR .RS Command or path to the SMT solver. This will use your environment's \fBPATH\fR variable, so if the solver is in one of your system directories you can simply provide the name of its binary. Note that this option has no effect unless you also pass \fB--smt-simplification\fR \fBon\fR. .RE .PP \fB--smt-prelude\fR \fITEXT\fR .RS Text to emit when communicating with the solver prior to sending the actual problem itself. You can use this to set a solver logic or other options. This option can be given multiple times and each argument will be passed to the solver on a separate line. .RE .PP \fB--smt-simplification\fR [\fBoff\fR | \fBon\fR] .RS Disable or enable using the SMT solver to simplify the input model. By default, this is automatic, in that it is turned \fBon\fR if you use any of the other SMT options or \fBoff\fR if you do not use them. .RE .SH LEGACY OPTIONS The following options should no longer be used but are documented here for completeness. .PP \fB--smt-logic\fR \fILOGIC\fR .RS Set the target logic used for communication with the SMT solver. This option is deprecated and you should use \fB--smt-prelude\fR instead. For example, \fB--smt-prelude AUFLIA\fR. .RE .SH AUTHOR All comments, questions and complaints should be directed to Matthew Fernandez . .SH LICENSE This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to rumur-2020.02.17/rumur/resources/000077500000000000000000000000001362265074000164505ustar00rootroot00000000000000rumur-2020.02.17/rumur/resources/README.rst000066400000000000000000000003701362265074000201370ustar00rootroot00000000000000This directory contains static code snippets that are included in the generated checker. That is, the code in this directory is not compiled into librumur or the rumur binary. Rather it is emitted verbatim into the generated checker's source code. rumur-2020.02.17/rumur/resources/header.c000066400000000000000000003360211362265074000200510ustar00rootroot00000000000000#ifndef __OPTIMIZE__ #ifdef __clang__ #ifdef __x86_64__ #warning you are compiling without optimizations enabled. I would suggest -march=native -O3 -mcx16. #else #warning you are compiling without optimizations enabled. I would suggest -march=native -O3. #endif #else #ifdef __x86_64__ #warning you are compiling without optimizations enabled. I would suggest -march=native -O3 -fwhole-program -mcx16. #else #warning you are compiling without optimizations enabled. I would suggest -march=native -O3 -fwhole-program. #endif #endif #endif #define value_to_string(v) ((value_t)(v)) #define raw_value_to_string(v) ((raw_value_t)(v)) /* A more powerful assert that treats the assertion as an assumption when * assertions are disabled. */ #ifndef NDEBUG #define ASSERT(expr) assert(expr) #elif defined(__clang__) #define ASSERT(expr) __builtin_assume(expr) #else /* GCC doesn't have __builtin_assume, so we need something else. */ #define ASSERT(expr) \ do { \ /* The following is an idiom for teaching the compiler an assumption. */ \ if (!(expr)) { \ __builtin_unreachable(); \ } \ } while (0) #endif #define BITS_TO_BYTES(size) ((size) / 8 + ((size) % 8 == 0 ? 0 : 1)) #define BITS_FOR(value) \ ((value) == 0 ? 0 : (sizeof(unsigned long long) * 8 - __builtin_clzll(value))) /* The size of the compressed state data in bytes. */ enum { STATE_SIZE_BYTES = BITS_TO_BYTES(STATE_SIZE_BITS) }; /* the size of auxliary members of the state struct */ enum { BOUND_BITS = BITS_FOR(BOUND) }; #if COUNTEREXAMPLE_TRACE != CEX_OFF || LIVENESS_COUNT > 0 #if defined(__linux__) && defined(__x86_64__) && !defined(__ILP32__) /* assume 5-level paging, and hence the top 2 bytes of any user pointer are * always 0 and not required. * https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt */ enum { PREVIOUS_BITS = 56 }; #else enum { PREVIOUS_BITS = sizeof(void*) * 8 }; #endif #else enum { PREVIOUS_BITS = 0 }; #endif #if COUNTEREXAMPLE_TRACE != CEX_OFF enum { RULE_TAKEN_BITS = BITS_FOR(RULE_TAKEN_LIMIT) }; #else enum { RULE_TAKEN_BITS = 0 }; #endif enum { STATE_OTHER_BYTES = BITS_TO_BYTES(BOUND_BITS + PREVIOUS_BITS + RULE_TAKEN_BITS) }; /* Implement _Thread_local for GCC <4.9, which is missing this. */ #if defined(__GNUC__) && defined(__GNUC_MINOR__) #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 9) #define _Thread_local __thread #endif #endif #ifdef __clang__ #define NONNULL _Nonnull #else #define NONNULL /* nothing; other compilers don't have _Nonnull */ #endif /* A word about atomics... There are two different atomic operation mechanisms * used in this code and it may not immediately be obvious why one was not * sufficient. The two are: * * 1. GCC __atomic built-ins: Used for variables that are sometimes accessed * with atomic semantics and sometimes as regular memory operations. The * C11 atomics cannot give us this and the __atomic built-ins are * implemented by the major compilers. * 2. GCC __sync built-ins: used for 128-bit atomic accesses on x86-64. It * seems the __atomic built-ins do not result in a CMPXCHG instruction, but * rather in a less efficient library call. See * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878. * * Though this is intended to be a C11 program, we avoid the C11 atomics to be * compatible with GCC <4.9. */ /* Identifier of the current thread. This counts up from 0 and thus is suitable * to use for, e.g., indexing into arrays. The initial thread has ID 0. */ static _Thread_local size_t thread_id; /* The threads themselves. Note that we have no element for the initial thread, * so *your* thread is 'threads[thread_id - 1]'. */ static pthread_t threads[THREADS - 1]; /* What we are currently doing. Either "warming up" (running single threaded * building up queue occupancy) or "free running" (running multithreaded). */ static enum { WARMUP, RUN } phase = WARMUP; /* Number of errors we've noted so far. If a thread sees this hit or exceed * MAX_ERRORS, they should attempt to exit gracefully as soon as possible. */ static unsigned long error_count; /* Number of rules that have been processed. There are two representations of * this: a thread-local count of how many rules we have fired thus far and a * global array of *final* counts of fired rules per-thread that is updated and * used as threads are exiting. The purpose of this duplication is to let the * compiler layout the thread-local variable in a cache-friendly way and use * this during checking, rather than having all threads contending on the global * array whose entries are likely all within the same cache line. */ static _Thread_local uintmax_t rules_fired_local; static uintmax_t rules_fired[THREADS]; /* Checkpoint to restore to after reporting an error. This is only used if we * are tolerating more than one error before exiting. */ static _Thread_local sigjmp_buf checkpoint; _Static_assert(MAX_ERRORS > 0, "illegal MAX_ERRORS value"); /* Whether we need to save and restore checkpoints. This is determined by * whether we ever need to perform the action "discard the current state and * skip to checking the next." This scenario can occur for two reasons: * 1. We are running multithreaded, have just found an error and have not yet * hit MAX_ERRORS. In this case we want to longjmp back to resume checking. * 2. We failed an assume statement. In this case we want to mark the current * state as invalid and resume checking with the next state. * In either scenario the actual longjmp performed is the same, but by knowing * statically whether either can occur we can avoid calling setjmp if both are * impossible. */ enum { JMP_BUF_NEEDED = MAX_ERRORS > 1 || ASSUME_STATEMENTS_COUNT > 0 }; /******************************************************************************* * Sandbox support. * * * * Because we're running generated code, it seems wise to use OS mechanisms to * * reduce our privileges, where possible. * ******************************************************************************/ static void sandbox(void) { if (!SANDBOX_ENABLED) { return; } #ifdef __APPLE__ { char *err; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" int r = sandbox_init(kSBXProfilePureComputation, SANDBOX_NAMED, &err); #pragma clang diagnostic pop if (__builtin_expect(r != 0, 0)) { fprintf(stderr, "sandbox_init failed: %s\n", err); free(err); exit(EXIT_FAILURE); } return; } #endif #ifdef __FreeBSD__ { if (__builtin_expect(cap_enter() != 0, 0)) { perror("cap_enter"); exit(EXIT_FAILURE); } return; } #endif #if defined(__linux__) #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) { /* Disable the addition of new privileges via execve and friends. */ int r = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); if (__builtin_expect(r != 0, 0)) { perror("prctl(PR_SET_NO_NEW_PRIVS) failed"); exit(EXIT_FAILURE); } /* A BPF program that traps on any syscall we want to disallow. */ static struct sock_filter filter[] = { /* Load syscall number. */ BPF_STMT(BPF_LD|BPF_W|BPF_ABS, offsetof(struct seccomp_data, nr)), /* Enable exiting. */ #ifdef __NR_exit_group BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_exit_group, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), #endif /* Enable syscalls used by printf. */ #ifdef __NR_fstat BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_fstat, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_fstat64 BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_fstat64, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_write BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_write, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), #endif /* Enable syscalls used by malloc. */ #ifdef __NR_brk BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_brk, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_mmap BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_mmap, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_mmap2 BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_mmap2, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), #endif #ifdef __NR_munmap BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_munmap, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW), #endif /* If we're running multithreaded, enable syscalls used by pthreads. */ #ifdef __NR_clone BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_clone, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_close BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_close, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_exit BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_exit, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_futex BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_futex, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_get_robust_list BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_get_robust_list, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_madvise BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_madvise, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_mprotect BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_mprotect, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_open // XXX: it would be nice to avoid open() but pthreads seems to open libgcc. BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_open, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_read BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_read, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_set_robust_list BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_set_robust_list, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif /* on platforms without vDSO support, time() makes an actual syscall, so * we need to allow them */ #ifdef __NR_gettimeofday BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_gettimeofday, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif #ifdef __NR_time BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_time, 0, 1), BPF_STMT(BPF_RET|BPF_K, THREADS > 1 ? SECCOMP_RET_ALLOW : SECCOMP_RET_TRAP), #endif /* Deny everything else. On a disallowed syscall, we trap instead of * killing to allow the user to debug the failure. If you are debugging * seccomp denials, strace the checker and find the number of the denied * syscall in the first si_value parameter reported in the terminating * SIG_SYS. */ BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_TRAP), }; static const struct sock_fprog filter_program = { .len = sizeof(filter) / sizeof(filter[0]), .filter = filter, }; /* Apply the above filter to ourselves. */ r = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &filter_program, 0, 0); if (__builtin_expect(r != 0, 0)) { perror("prctl(PR_SET_SECCOMP) failed"); exit(EXIT_FAILURE); } return; } #endif #endif #ifdef __OpenBSD__ { if (__builtin_expect(pledge("stdio", "") != 0, 0)) { perror("pledge"); exit(EXIT_FAILURE); } return; } #endif /* No sandbox available. */ fprintf(stderr, "no sandboxing facilities available\n"); exit(EXIT_FAILURE); } /******************************************************************************/ // ANSI colour code support. static bool istty; static const char *green() { if (COLOR == ON || (COLOR == AUTO && istty)) return "\033[32m"; return ""; } static const char *red() { if (COLOR == ON || (COLOR == AUTO && istty)) return "\033[31m"; return ""; } static const char *yellow() { if (COLOR == ON || (COLOR == AUTO && istty)) return "\033[33m"; return ""; } static const char *bold() { if (COLOR == ON || (COLOR == AUTO && istty)) return "\033[1m"; return ""; } static const char *reset() { if (COLOR == ON || (COLOR == AUTO && istty)) return "\033[0m"; return ""; } #ifdef __SIZEOF_INT128__ /* if we have the type `__int128` */ #define UINT128_MAX \ ((((unsigned __int128)UINT64_MAX) << 64) | ((unsigned __int128)UINT64_MAX)) #define INT128_MAX ((((__int128)INT64_MAX) << 64) | ((__int128)UINT64_MAX)) #define INT128_MIN (-INT128_MAX - 1) struct string_buffer { char data[41]; }; static struct string_buffer value_u128_to_string(unsigned __int128 v) { struct string_buffer buffer; if (v == 0) { buffer.data[0] = '0'; buffer.data[1] = '\0'; return buffer; } size_t i = sizeof(buffer.data); while (v != 0) { i--; buffer.data[i] = '0' + v % 10; v /= 10; } memmove(buffer.data, &buffer.data[i], sizeof(buffer) - i); buffer.data[sizeof(buffer) - i] = '\0'; return buffer; } static __attribute__((unused)) struct string_buffer value_128_to_string( __int128 v) { if (v == INT128_MIN) { struct string_buffer buffer; strcpy(buffer.data, "-170141183460469231731687303715884105728"); return buffer; } bool negative = v < 0; if (negative) { v = -v; } struct string_buffer buffer = value_u128_to_string(v); if (negative) { memmove(&buffer.data[1], buffer.data, strlen(buffer.data) + 1); buffer.data[0] = '-'; } return buffer; } #endif /******************************************************************************* * MurmurHash by Austin Appleby * * * * More information on this at https://github.com/aappleby/smhasher/ * ******************************************************************************/ static uint64_t MurmurHash64A(const void *NONNULL key, size_t len) { static const uint64_t seed = 0; static const uint64_t m = UINT64_C(0xc6a4a7935bd1e995); static const unsigned r = 47; uint64_t h = seed ^ (len * m); const unsigned char *data = key; const unsigned char *end = data + len / sizeof(uint64_t) * sizeof(uint64_t); while (data != end) { uint64_t k; memcpy(&k, data, sizeof(k)); data += sizeof(k); k *= m; k ^= k >> r; k *= m; h ^= k; h *= m; } const unsigned char *data2 = data; switch (len & 7) { case 7: h ^= (uint64_t)data2[6] << 48; /* fall through */ case 6: h ^= (uint64_t)data2[5] << 40; /* fall through */ case 5: h ^= (uint64_t)data2[4] << 32; /* fall through */ case 4: h ^= (uint64_t)data2[3] << 24; /* fall through */ case 3: h ^= (uint64_t)data2[2] << 16; /* fall through */ case 2: h ^= (uint64_t)data2[1] << 8; /* fall through */ case 1: h ^= (uint64_t)data2[0]; h *= m; } h ^= h >> r; h *= m; h ^= h >> r; return h; } /******************************************************************************/ /* Signal an out-of-memory condition and terminate abruptly. */ static _Noreturn void oom(void) { fputs("out of memory", stderr); exit(EXIT_FAILURE); } static void *xmalloc(size_t size) { void *p = malloc(size); if (__builtin_expect(p == NULL, 0)) { oom(); } return p; } static void *xcalloc(size_t count, size_t size) { void *p = calloc(count, size); if (__builtin_expect(p == NULL, 0)) { oom(); } return p; } /* A lock that should be held whenever printing to stdout or stderr. This is a * way to prevent the output of one thread being interleaved with the output of * another. */ static pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER; static void print_lock(void) { int r __attribute__((unused)) = pthread_mutex_lock(&print_mutex); assert(r == 0); } static void print_unlock(void) { int r __attribute__((unused)) = pthread_mutex_unlock(&print_mutex); assert(r == 0); } static void xml_printf(const char *NONNULL s) { while (*s != '\0') { switch (*s) { case '"': printf("""); break; case '<': printf("<"); break; case '>': printf(">"); break; case '&': printf("&"); break; default: printf("%c", *s); break; } s++; } } /* Support for tracing specific operations. This can be enabled during checker * generation with '--trace ...' and is useful for debugging Rumur itself. */ static __attribute__((format(printf, 1, 2))) void trace(const char *NONNULL fmt, ...) { va_list ap; va_start(ap, fmt); print_lock(); (void)fprintf(stderr, "%sTRACE%s:", yellow(), reset()); (void)vfprintf(stderr, fmt, ap); (void)fprintf(stderr, "\n"); print_unlock(); va_end(ap); } /* Wrap up trace() as a macro. It looks as if the following could just be * incorporated into trace(). However, present compilers seem unwilling to * inline varargs functions or do interprocedural analysis across a call to one. * As a result, the compiler does not notice when tracing is disabled and a call * to trace() would be a no-op that can be elided. By making the call a macro we * make the category comparison visible to the compiler's optimising passes. */ #define TRACE(category, args...) \ do { \ if ((category) & TRACES_ENABLED) { \ trace(args); \ } \ } while (0) /******************************************************************************* * Arithmetic wrappers * * * * For compilers that support them, we call the overflow built-ins to check * * undefined operations during arithmetic. For others, we just emit the bare * * operation. * ******************************************************************************/ #if defined(__clang__) || (defined(__GNUC__) && __GNUC__ >= 5) #define ADD(a, b, c) __builtin_add_overflow((a), (b), (c)) #define MUL(a, b, c) __builtin_mul_overflow((a), (b), (c)) #define SUB(a, b, c) __builtin_sub_overflow((a), (b), (c)) #else #define ADD(a, b, c) ({ *(c) = (a) + (b); false; }) #define MUL(a, b, c) ({ *(c) = (a) * (b); false; }) #define SUB(a, b, c) ({ *(c) = (a) - (b); false; }) #endif /******************************************************************************/ /* The state of the current model. */ struct state { #if LIVENESS_COUNT > 0 uintptr_t liveness[LIVENESS_COUNT / sizeof(uintptr_t) / CHAR_BIT + (LIVENESS_COUNT % sizeof(uintptr_t) == 0 && LIVENESS_COUNT / sizeof(uintptr_t) % CHAR_BIT == 0 ? 0 : 1)]; #endif uint8_t data[STATE_SIZE_BYTES]; #if PACK_STATE /* the following effective fields are packed into here: * * * uint64_t bound; * * const struct state *previous; * * uint64_t rule_taken; * * They are bit-packed, so may take up less space. E.g. if the maximum value * of `bound` is known to be 5, it will be stored in 3 bits instead of 64. */ uint8_t other[STATE_OTHER_BYTES]; #else uint64_t bound; const struct state *previous; uint64_t rule_taken; #endif }; struct handle { uint8_t *base; size_t offset; size_t width; }; static struct handle handle_align(struct handle h) { ASSERT(h.offset < CHAR_BIT && "handle has an offset outside the base byte"); size_t width = h.width + h.offset; if (width % 8 != 0) { width += 8 - width % 8; } return (struct handle){ .base = h.base, .offset = 0, .width = width, }; } static bool is_big_endian(void) { union { uint32_t x; uint8_t y[sizeof(uint32_t)]; } z; z.y[0] = 1; z.y[1] = 2; z.y[2] = 3; z.y[3] = 4; return z.x == 0x01020304; } static uint64_t copy_out64(const uint8_t *p, size_t extent) { ASSERT(extent <= sizeof(uint64_t)); uint64_t x = 0; memcpy(&x, p, extent); if (is_big_endian()) { x = __builtin_bswap64(x); } return x; } #ifdef __SIZEOF_INT128__ /* if we have the type `__int128` */ static unsigned __int128 byte_swap128(unsigned __int128 x) { union { unsigned __int128 a; uint64_t b[2]; } in, out; in.a = x; out.b[0] = __builtin_bswap64(in.b[1]); out.b[1] = __builtin_bswap64(in.b[0]); return out.a; } static unsigned __int128 copy_out128(const uint8_t *p, size_t extent) { ASSERT(extent <= sizeof(unsigned __int128)); unsigned __int128 x = 0; memcpy(&x, p, extent); if (is_big_endian()) { x = byte_swap128(x); } return x; } #endif /* If you are in the Rumur repository modifying the following function, remember * to also update ../../misc/read-raw.smt2. */ static __attribute__((pure)) uint64_t read_raw(struct handle h) { /* a uint64_t is the maximum value we support reading */ ASSERT(h.width <= 64 && "read of too wide value"); if (h.width == 0) { return 0; } /* Generate a handle that is offset- and width-aligned on byte boundaries. * Essentially, we widen the handle to align it. The motivation for this is * that we can only do byte-granularity reads, so we need to "over-read" if we * have an unaligned handle. */ struct handle aligned = handle_align(h); ASSERT(aligned.offset == 0); /* The code below attempts to provide four alternatives for reading out the * bits corresponding to a value of simple type referenced by a handle, and to * give the compiler enough hints to steer it towards picking one of these and * removing the other three as dead code: * * 1. Read into a single 64-bit variable. Enabled when the maximum width for * an unaligned handle spans 0 - 8 bytes. * 2. Read into two 64-bit variables and then combine the result using * shifts and ORs. Enabled when the maximum width for an unaligned handle * spans 9 - 16 bytes and the compiler does not provide the `__int128` * type. * 3. Read into a single 128-bit variable. Enabled when the compiler does * provide the `__int128` type and the maximum width for an unaligned * handle spans 9 - 16 bytes. * 4. Read into two 128-bit chunks, and then combine the result using shifts * and ORs. Enabled when the compiler provides the `__int128` type and * the maximum width for an unaligned handle spans 17 - 32 bytes. */ #ifdef __SIZEOF_INT128__ /* if we have the type `__int128` */ /* If a byte-unaligned value_t cannot be fully read into a single uint64_t * using byte-aligned reads... */ if (aligned.width > (sizeof(uint64_t) - 1) * 8) { /* Read the low double-word of this (possibly quad-word-sized) value. */ unsigned __int128 low = 0; size_t low_size = aligned.width / 8; /* optimisation hint: */ ASSERT(low_size <= sizeof(low) || aligned.width > (sizeof(low) - 1) * 8); if (low_size > sizeof(low)) { low_size = sizeof(low); } { const uint8_t *src = aligned.base; low = copy_out128(src, low_size); } low >>= h.offset; size_t high_size = aligned.width / 8 - low_size; /* If the value could not be read into a single double-word... */ ASSERT(high_size == 0 || aligned.width > (sizeof(low) - 1) * 8); if (high_size != 0) { unsigned __int128 high = 0; const uint8_t *src = aligned.base + sizeof(low); high = copy_out128(src, high_size); high <<= sizeof(low) * 8 - h.offset; /* Combine the two halves into a single double-word. */ low |= high; } if (h.width < sizeof(low) * 8) { unsigned __int128 mask = (((unsigned __int128)1) << h.width) - 1; low &= mask; } ASSERT(low <= UINT64_MAX && "read of value larger than a uint64_t"); return (uint64_t)low; } #endif /* Read the low word of this (possibly two-word-sized) value. */ uint64_t low = 0; size_t low_size = aligned.width / 8; /* optimisation hint: */ ASSERT(low_size <= sizeof(low) || aligned.width > (sizeof(low) - 1) * 8); if (low_size > sizeof(low)) { low_size = sizeof(low); } { const uint8_t *src = aligned.base; low = copy_out64(src, low_size); } low >>= h.offset; size_t high_size = aligned.width / 8 - low_size; /* If the value could not be read into a single word... */ ASSERT(high_size == 0 || aligned.width > (sizeof(low) - 1) * 8); if (high_size != 0) { const uint8_t *src = aligned.base + sizeof(low); uint64_t high = copy_out64(src, high_size); high <<= sizeof(low) * 8 - h.offset; /* Combine the high and low words. Note that we know we can store the final * result in a single word because the width is guaranteed to be <= 64. */ low |= high; } if (h.width < sizeof(low) * 8) { uint64_t mask = (UINT64_C(1) << h.width) - 1; low &= mask; } return low; } static void copy_in64(uint8_t *p, uint64_t v, size_t extent) { ASSERT(extent <= sizeof(v)); if (is_big_endian()) { v = __builtin_bswap64(v); } memcpy(p, &v, extent); } #ifdef __SIZEOF_INT128__ /* if we have the type `__int128` */ static void copy_in128(uint8_t *p, unsigned __int128 v, size_t extent) { ASSERT(extent <= sizeof(v)); if (is_big_endian()) { v = byte_swap128(v); } memcpy(p, &v, extent); } #endif /* If you are in the Rumur repository modifying the following function, remember * to also update ../../misc/write-raw.smt2. */ static void write_raw(struct handle h, uint64_t v) { if (h.width == 0) { return; } /* sanitise input value */ if (h.width < sizeof(v) * 8) { v &= (UINT64_C(1) << h.width) - 1; } /* Generate a offset- and width-aligned handle on byte boundaries. */ struct handle aligned = handle_align(h); ASSERT(aligned.offset == 0); #ifdef __SIZEOF_INT128__ /* if we have the type `__int128` */ /* If a byte-unaligned value_t cannot be fully written within a single * byte-aligned uint64_t... */ if (aligned.width > (sizeof(uint64_t) - 1) * 8) { /* Read the low double-word of this region. */ unsigned __int128 low = 0; size_t low_size = aligned.width / 8; ASSERT(low_size <= sizeof(low) || aligned.width > (sizeof(low) - 1) * 8); if (low_size > sizeof(low)) { low_size = sizeof(low); } { const uint8_t *src = aligned.base; low = copy_out128(src, low_size); } { unsigned __int128 or_mask = ((unsigned __int128)v) << h.offset; if (low_size < sizeof(low)) { or_mask &= (((unsigned __int128)1) << (low_size * 8)) - 1; } unsigned __int128 and_mask = (((unsigned __int128)1) << h.offset) - 1; if (h.width + h.offset < sizeof(low) * 8) { size_t high_bits = aligned.width - h.offset - h.width; and_mask |= ((((unsigned __int128)1) << high_bits) - 1) << (low_size * 8 - high_bits); } low = (low & and_mask) | or_mask; } { uint8_t *dest = aligned.base; copy_in128(dest, low, low_size); } /* Now do the second double-word if necessary. */ size_t high_size = aligned.width / 8 - low_size; ASSERT(high_size == 0 || aligned.width > (sizeof(low) - 1) * 8); if (high_size != 0) { unsigned __int128 high = 0; { const uint8_t *src = aligned.base + sizeof(low); high = copy_out128(src, high_size); } { unsigned __int128 or_mask = ((unsigned __int128)v) >> (sizeof(low) * 8 - h.offset); unsigned __int128 and_mask = ~((((unsigned __int128)1) << (h.width + h.offset - sizeof(low) * 8)) - 1); high = (high & and_mask) | or_mask; } { uint8_t *dest = aligned.base + sizeof(low); copy_in128(dest, high, high_size); } } return; } #endif /* Replicate the above logic for uint64_t. */ uint64_t low = 0; size_t low_size = aligned.width / 8; ASSERT(low_size <= sizeof(low) || aligned.width > (sizeof(low) - 1) * 8); if (low_size > sizeof(low)) { low_size = sizeof(low); } { const uint8_t *src = aligned.base; low = copy_out64(src, low_size); } { uint64_t or_mask = ((uint64_t)v) << h.offset; if (low_size < sizeof(low)) { or_mask &= (UINT64_C(1) << (low_size * 8)) - 1; } uint64_t and_mask = (UINT64_C(1) << h.offset) - 1; if (h.width + h.offset < sizeof(low) * 8) { size_t high_bits = aligned.width - h.offset - h.width; and_mask |= ((UINT64_C(1) << high_bits) - 1) << (low_size * 8 - high_bits); } low = (low & and_mask) | or_mask; } { uint8_t *dest = aligned.base; copy_in64(dest, low, low_size); } size_t high_size = aligned.width / 8 - low_size; ASSERT(high_size == 0 || aligned.width > (sizeof(low) - 1) * 8); if (high_size != 0) { uint64_t high = 0; { const uint8_t *src = aligned.base + sizeof(low); high = copy_out64(src, high_size); } { uint64_t or_mask = ((uint64_t)v) >> (sizeof(low) * 8 - h.offset); uint64_t and_mask = ~((UINT64_C(1) << (h.width + h.offset - sizeof(low) * 8)) - 1); high = (high & and_mask) | or_mask; } { uint8_t *dest = aligned.base + sizeof(low); copy_in64(dest, high, high_size); } } } #if BOUND > 0 #if PACK_STATE static struct handle state_bound_handle(const struct state *NONNULL s) { struct handle h = (struct handle){ .base = (uint8_t*)s->other, .offset = 0, .width = BITS_FOR(BOUND), }; return h; } #endif _Static_assert((uintmax_t)BOUND <= UINT64_MAX, "bound limit does not fit in a uint64_t"); static __attribute__((pure)) uint64_t state_bound_get( const struct state *NONNULL s) { assert(s != NULL); #if PACK_STATE struct handle h = state_bound_handle(s); return read_raw(h); #else return s->bound; #endif } static void state_bound_set(struct state *NONNULL s, uint64_t bound) { assert(s != NULL); #if PACK_STATE struct handle h = state_bound_handle(s); write_raw(h, bound); #else s->bound = bound; #endif } #endif #if COUNTEREXAMPLE_TRACE != CEX_OFF || LIVENESS_COUNT > 0 #if PACK_STATE static struct handle state_previous_handle(const struct state *NONNULL s) { size_t offset = BOUND_BITS; struct handle h = (struct handle){ .base = (uint8_t*)s->other + offset / 8, .offset = offset % 8, .width = PREVIOUS_BITS, }; return h; } #endif static __attribute__((pure)) const struct state *state_previous_get( const struct state *NONNULL s) { #if PACK_STATE struct handle h = state_previous_handle(s); return (const struct state*)(uintptr_t)read_raw(h); #else return s->previous; #endif } static void state_previous_set(struct state *NONNULL s, const struct state *previous) { #if PACK_STATE #if defined(__linux__) && defined(__x86_64__) && !defined(__ILP32__) ASSERT(((uintptr_t)previous >> PREVIOUS_BITS) == 0 && "upper 2 bytes of pointer are non-zero (not using 5-level paging?)"); #endif struct handle h = state_previous_handle(s); write_raw(h, (uint64_t)(uintptr_t)previous); #else s->previous = previous; #endif } #endif #if COUNTEREXAMPLE_TRACE != CEX_OFF #if PACK_STATE static struct handle state_rule_taken_handle(const struct state *NONNULL s) { size_t offset = BOUND_BITS + PREVIOUS_BITS; struct handle h = (struct handle){ .base = (uint8_t*)s->other + offset / 8, .offset = offset % 8, .width = RULE_TAKEN_BITS, }; return h; } #endif static __attribute__((pure)) uint64_t state_rule_taken_get( const struct state *NONNULL s) { assert(s != NULL); #if PACK_STATE struct handle h = state_rule_taken_handle(s); return read_raw(h); #else return s->rule_taken; #endif } static void state_rule_taken_set(struct state *NONNULL s, uint64_t rule_taken) { assert(s != NULL); #if PACK_STATE struct handle h = state_rule_taken_handle(s); write_raw(h, rule_taken); #else s->rule_taken = rule_taken; #endif } #endif /******************************************************************************* * State allocator. * * * * The following implements a simple bump allocator for states. The purpose of * * this (rather than simply mallocing individual states) is to speed up * * allocation by taking global locks less frequently and decrease allocator * * metadata overhead. * ******************************************************************************/ /* An initial size of thread-local allocator pools ~8MB. */ static _Thread_local size_t arena_count = (sizeof(struct state) > 8 * 1024 * 1024) ? 1 : (8 * 1024 * 1024 / sizeof(struct state)); static _Thread_local struct state *arena_base; static _Thread_local struct state *arena_limit; static struct state *state_new(void) { if (arena_base == arena_limit) { /* Allocation pool is empty. We need to set up a new pool. */ for (;;) { if (arena_count == 1) { arena_base = xmalloc(sizeof(*arena_base)); } else { arena_base = calloc(arena_count, sizeof(*arena_base)); if (__builtin_expect(arena_base == NULL, 0)) { /* Memory pressure high. Decrease our attempted allocation and try * again. */ arena_count /= 2; continue; } } arena_limit = arena_base + arena_count; break; } } assert(arena_base != NULL); assert(arena_base != arena_limit); struct state *s = arena_base; arena_base++; return s; } static void state_free(struct state *s) { if (s == NULL) { return; } assert(s + 1 == arena_base); arena_base--; } /******************************************************************************* * statistics for memory usage * * * * This functionality is only used when `--trace memory_usage` is given on the * * command line. * ******************************************************************************/ /* number of allocated state structs per depth of expansion */ static size_t allocated[BOUND == 0 ? 1 : (BOUND + 1)]; /* note a new allocation of a state struct at the given depth */ static void register_allocation(size_t depth) { /* if we are not tracing memory usage, make this a no-op */ if (!(TC_MEMORY_USAGE & TRACES_ENABLED)) { return; } ASSERT(depth < sizeof(allocated) / sizeof(allocated[0]) && "out of range access to allocated array"); /* increment the number of known allocated states, avoiding an expensive * atomic if we are single-threaded */ if (THREADS == 1) { allocated[depth]++; } else { (void)__sync_add_and_fetch(&allocated[depth], 1); } } /* print a summary of the allocation results we have accrued */ static void print_allocation_summary(void) { /* it is assumed we are running single-threaded here and do not need atomic * accesses */ if (BOUND == 0) { TRACE(TC_MEMORY_USAGE, "allocated %zu state structure(s), totaling %zu " "bytes", allocated[0], allocated[0] * sizeof(struct state)); } else { for (size_t i = 0; i < sizeof(allocated) / sizeof(allocated[0]); i++) { if (allocated[i] == 0) { /* no state at this depth was reached, therefore no states at deeper * depths were reached either and we are done */ for (size_t j = i + 1; j < sizeof(allocated) / sizeof(allocated[0]); j++) { assert(allocated[j] == 0 && "state allocated at a deeper depth than an empty level"); } break; } TRACE(TC_MEMORY_USAGE, "depth %zu: allocated %zu state structure(s), " "totaling %zu bytes", i, allocated[i], allocated[i] * sizeof(struct state)); } } } /******************************************************************************/ /* Print a counterexample trace terminating at the given state. This function * assumes that the caller already holds print_mutex. */ static void print_counterexample( const struct state *NONNULL s __attribute__((unused))); /* "Exit" the current thread. This takes into account which thread we are. I.e. * the correct way to exit the checker is for every thread to eventually call * this function. */ static _Noreturn int exit_with(int status); static __attribute__((format(printf, 2, 3))) _Noreturn void error( const struct state *NONNULL s, const char *NONNULL fmt, ...) { unsigned long prior_errors = __atomic_fetch_add(&error_count, 1, __ATOMIC_SEQ_CST); if (__builtin_expect(prior_errors < MAX_ERRORS, 1)) { print_lock(); va_list ap; va_start(ap, fmt); if (MACHINE_READABLE_OUTPUT) { printf("\n", (s == NULL || COUNTEREXAMPLE_TRACE == CEX_OFF) ? "false" : "true"); printf(""); { va_list ap2; va_copy(ap2, ap); int size = vsnprintf(NULL, 0, fmt, ap2); va_end(ap2); if (__builtin_expect(size < 0, 0)) { fputs("vsnprintf failed", stderr); exit(EXIT_FAILURE); } char *buffer = xmalloc(size); if (__builtin_expect(vsnprintf(buffer, size, fmt, ap) != size, 0)) { fputs("vsnprintf failed", stderr); exit(EXIT_FAILURE); } xml_printf(buffer); free(buffer); } printf("\n"); if (s != NULL && COUNTEREXAMPLE_TRACE != CEX_OFF) { print_counterexample(s); } printf("\n"); } else { if (s != NULL) { printf("The following is the error trace for the error:\n\n"); } else { printf("Result:\n\n"); } printf("\t%s%s", red(), bold()); vprintf(fmt, ap); printf("%s\n\n", reset()); if (s != NULL && COUNTEREXAMPLE_TRACE != CEX_OFF) { print_counterexample(s); printf("End of the error trace.\n\n"); } } va_end(ap); print_unlock(); } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif if (prior_errors < MAX_ERRORS - 1) { #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif assert(JMP_BUF_NEEDED && "longjmping without a setup jmp_buf"); siglongjmp(checkpoint, 1); } exit_with(EXIT_FAILURE); } static void deadlock(const struct state *NONNULL s) { if (JMP_BUF_NEEDED) { if (sigsetjmp(checkpoint, 0)) { /* error() longjmped back to us. */ return; } } error(s, "deadlock"); } static int state_cmp(const struct state *NONNULL a, const struct state *NONNULL b) { return memcmp(a->data, b->data, sizeof(a->data)); } static bool state_eq(const struct state *NONNULL a, const struct state *NONNULL b) { return state_cmp(a, b) == 0; } static struct state *state_dup(const struct state *NONNULL s) { struct state *n = state_new(); memcpy(n->data, s->data, sizeof(n->data)); #if COUNTEREXAMPLE_TRACE != CEX_OFF || LIVENESS_COUNT > 0 state_previous_set(n, s); #endif #if BOUND > 0 assert(state_bound_get(s) < BOUND && "exceeding bounded exploration depth"); state_bound_set(n, state_bound_get(s) + 1); #endif #if LIVENESS_COUNT > 0 memset(n->liveness, 0, sizeof(n->liveness)); #endif return n; } static size_t state_hash(const struct state *NONNULL s) { return (size_t)MurmurHash64A(s->data, sizeof(s->data)); } #if COUNTEREXAMPLE_TRACE != CEX_OFF static __attribute__((unused)) size_t state_depth( const struct state *NONNULL s) { #if BOUND > 0 uint64_t bound = state_bound_get(s); ASSERT(bound <= BOUND && "claimed state bound exceeds limit"); return (size_t)bound + 1; #else size_t d = 0; while (s != NULL) { d++; s = state_previous_get(s); } return d; #endif } #endif /* A type-safe const cast. */ static __attribute__((unused)) struct state *state_drop_const(const struct state *s) { return (struct state*)s; } /* These functions are generated. */ static void state_canonicalise_heuristic(struct state *NONNULL s); static void state_canonicalise_exhaustive(struct state *NONNULL s); static void state_canonicalise(struct state *NONNULL s) { assert(s != NULL && "attempt to canonicalise NULL state"); switch (SYMMETRY_REDUCTION) { case SYMMETRY_REDUCTION_OFF: break; case SYMMETRY_REDUCTION_HEURISTIC: state_canonicalise_heuristic(s); break; case SYMMETRY_REDUCTION_EXHAUSTIVE: state_canonicalise_exhaustive(s); break; } } /* This function is generated. */ static __attribute__((unused)) void state_print_field_offsets(void); /* Print a state to stderr. This function is generated. This function assumes * that the caller already holds print_mutex. */ static __attribute__((unused)) void state_print(const struct state *previous, const struct state *NONNULL s); /* Print the first rule that resulted in s. This function is generated. This * function assumes that the caller holds print_mutex. */ static __attribute__((unused)) void print_transition( const struct state *NONNULL s); static void print_counterexample( const struct state *NONNULL s __attribute__((unused))) { assert(s != NULL && "missing state in request for counterexample trace"); #if COUNTEREXAMPLE_TRACE != CEX_OFF /* Construct an array of the states we need to print by walking backwards to * the initial starting state. */ size_t trace_length = state_depth(s); const struct state **cex = xcalloc(trace_length, sizeof(cex[0])); { size_t i = trace_length - 1; for (const struct state *p = s; p != NULL; p = state_previous_get(p)) { assert(i < trace_length && "error in counterexample trace traversal " "logic"); cex[i] = p; i--; } } for (size_t i = 0; i < trace_length; i++) { const struct state *current = cex[i]; const struct state *previous = i == 0 ? NULL : cex[i - 1]; print_transition(current); if (MACHINE_READABLE_OUTPUT) { printf("\n"); } state_print(COUNTEREXAMPLE_TRACE == FULL ? NULL : previous, current); if (MACHINE_READABLE_OUTPUT) { printf("\n"); } else { printf("----------\n\n"); } } free(cex); #endif } static __attribute__((unused)) struct handle state_handle( const struct state *NONNULL s, size_t offset, size_t width) { assert(sizeof(s->data) * CHAR_BIT - width >= offset && "generating an out of " "bounds handle in state_handle()"); return (struct handle){ .base = (uint8_t*)s->data + offset / CHAR_BIT, .offset = offset % CHAR_BIT, .width = width, }; } static raw_value_t handle_read_raw(const struct state *NONNULL s, struct handle h) { /* Check if this read is larger than the variable we will store it in. This * can only occur if the user has manually overridden value_t with the * --value-type command line argument. */ if (__builtin_expect(h.width > sizeof(raw_value_t) * 8, 0)) { error(s, "read of a handle that is wider than the value type"); } ASSERT(h.width <= MAX_SIMPLE_WIDTH && "read of a handle that is larger than " "the maximum width of a simple type in this model"); uint64_t raw = (raw_value_t)read_raw(h); TRACE(TC_HANDLE_READS, "read value %" PRIRAWVAL " from handle { %p, %zu, %zu }", raw_value_to_string(raw), h.base, h.offset, h.width); return raw; } static value_t decode_value(value_t lb, value_t ub, raw_value_t v) { value_t dest = 0; bool r __attribute__((unused)) = SUB(v, 1, &v) || ADD(v, lb, &dest) || dest < lb || dest > ub; ASSERT(!r && "read of out-of-range value"); return dest; } static __attribute__((unused)) value_t handle_read(const char *NONNULL context, const char *rule_name, const char *NONNULL name, const struct state *NONNULL s, value_t lb, value_t ub, struct handle h) { assert(context != NULL); assert(name != NULL); /* If we happen to be reading from the current state, do a sanity check that * we're only reading within bounds. */ assert((h.base != (uint8_t*)s->data /* not a read from the current state */ || sizeof(s->data) * CHAR_BIT - h.width >= h.offset) /* in bounds */ && "out of bounds read in handle_read()"); raw_value_t dest = handle_read_raw(s, h); if (__builtin_expect(dest == 0, 0)) { error(s, "%sread of undefined value in %s%s%s", context, name, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return decode_value(lb, ub, dest); } static void handle_write_raw(const struct state *NONNULL s, struct handle h, raw_value_t value) { /* Check if this write is larger than the variable we will are reading from. * This can only occur if the user has manually overridden value_t with the * --value-type command line argument. */ if (__builtin_expect(h.width > sizeof(raw_value_t) * 8, 0)) { error(s, "write of a handle that is wider than the value type"); } ASSERT(h.width <= MAX_SIMPLE_WIDTH && "write to a handle that is larger than " "the maximum width of a simple type in this model"); TRACE(TC_HANDLE_WRITES, "writing value %" PRIRAWVAL " to handle { %p, %zu, %zu }", raw_value_to_string(value), h.base, h.offset, h.width); #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif ASSERT((uintmax_t)value <= UINT64_MAX && "truncating value during handle_write_raw"); #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif write_raw(h, (uint64_t)value); } static __attribute__((unused)) void handle_write(const char *NONNULL context, const char *rule_name, const char *NONNULL name, const struct state *NONNULL s, value_t lb, value_t ub, struct handle h, value_t value) { assert(context != NULL); assert(name != NULL); /* If we happen to be writing to the current state, do a sanity check that * we're only writing within bounds. */ assert((h.base != (uint8_t*)s->data /* not a write to the current state */ || sizeof(s->data) * CHAR_BIT - h.width >= h.offset) /* in bounds */ && "out of bounds write in handle_write()"); raw_value_t r; if (__builtin_expect(value < lb || value > ub || SUB(value, lb, &r) || ADD(r, 1, &r), 0)) { error(s, "%swrite of out-of-range value into %s%s%s", context, name, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } handle_write_raw(s, h, r); } static __attribute__((unused)) void handle_zero(struct handle h) { uint8_t *p = h.base + h.offset / 8; /* Zero out up to a byte-aligned offset. */ if (h.offset % 8 != 0) { uint8_t mask = (UINT8_C(1) << (h.offset % 8)) - 1; if (h.width < 8 - h.offset % 8) { mask |= UINT8_MAX & ~((UINT8_C(1) << (h.offset % 8 + h.width)) - 1); } *p &= mask; p++; if (h.width < 8 - h.offset % 8) { return; } h.width -= 8 - h.offset % 8; } /* Zero out as many bytes as we can. */ memset(p, 0, h.width / 8); p += h.width / 8; h.width -= h.width / 8 * 8; /* Zero out the trailing bits in the final byte. */ if (h.width > 0) { uint8_t mask = ~((UINT8_C(1) << h.width) - 1); *p &= mask; } } static __attribute__((unused)) void handle_copy(struct handle a, struct handle b) { ASSERT(a.width == b.width && "copying between handles of different sizes"); /* FIXME: This does a bit-by-bit copy which almost certainly could be * accelerated by detecting byte-boundaries and complementary alignment and * then calling memcpy when possible. */ for (size_t i = 0; i < a.width; i++) { uint8_t *dst = a.base + (a.offset + i) / 8; size_t dst_off = (a.offset + i) % 8; const uint8_t *src = b.base + (b.offset + i) / 8; size_t src_off = (b.offset + i) % 8; uint8_t or_mask = ((*src >> src_off) & UINT8_C(1)) << dst_off; uint8_t and_mask = ~(UINT8_C(1) << dst_off); *dst = (*dst & and_mask) | or_mask; } } static __attribute__((unused)) bool handle_eq(struct handle a, struct handle b) { ASSERT(a.width == b.width && "comparing handles of different sizes"); /* FIXME: as with handle_copy, we do a bit-by-bit comparison which could be * made more efficient. */ for (size_t i = 0; i < a.width; i++) { uint8_t *x = a.base + (a.offset + i) / 8; size_t x_off = (a.offset + i) % 8; bool x_bit = (*x >> x_off) & 0x1; const uint8_t *y = b.base + (b.offset + i) / 8; size_t y_off = (b.offset + i) % 8; bool y_bit = (*y >> y_off) & 0x1; if (x_bit != y_bit) { return false; } } return true; } static __attribute__((unused)) struct handle handle_narrow(struct handle h, size_t offset, size_t width) { ASSERT(h.offset + offset + width <= h.offset + h.width && "narrowing a handle with values that actually expand it"); size_t r __attribute__((unused)); assert(!ADD(h.offset, offset, &r) && "narrowing handle overflows a size_t"); return (struct handle){ .base = h.base + (h.offset + offset) / CHAR_BIT, .offset = (h.offset + offset) % CHAR_BIT, .width = width, }; } static __attribute__((unused)) struct handle handle_index( const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, size_t element_width, value_t index_min, value_t index_max, struct handle root, value_t index) { assert(expr != NULL); if (__builtin_expect(index < index_min || index > index_max, 0)) { error(s, "%sindex out of range in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } size_t r1, r2; if (__builtin_expect(SUB(index, index_min, &r1) || MUL(r1, element_width, &r2), 0)) { error(s, "%soverflow when indexing array in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } size_t r __attribute__((unused)); assert(!ADD(root.offset, r2, &r) && "indexing handle overflows a size_t"); return (struct handle){ .base = root.base + (root.offset + r2) / CHAR_BIT, .offset = (root.offset + r2) % CHAR_BIT, .width = element_width, }; } static __attribute__((unused)) value_t handle_isundefined( const struct state *NONNULL s, struct handle h) { raw_value_t v = handle_read_raw(s, h); return v == 0; } /* Overflow-safe helpers for doing bounded arithmetic. */ static __attribute__((unused)) value_t add(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a, value_t b) { assert(context != NULL); assert(expr != NULL); value_t r; if (__builtin_expect(ADD(a, b, &r), 0)) { error(s, "%sinteger overflow in addition in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return r; } static __attribute__((unused)) value_t sub(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a, value_t b) { assert(context != NULL); assert(expr != NULL); value_t r; if (__builtin_expect(SUB(a, b, &r), 0)) { error(s, "%sinteger overflow in subtraction in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return r; } static __attribute__((unused)) value_t mul(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a, value_t b) { assert(context != NULL); assert(expr != NULL); value_t r; if (__builtin_expect(MUL(a, b, &r), 0)) { error(s, "%sinteger overflow in multiplication in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return r; } static __attribute__((unused)) value_t divide(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a, value_t b) { assert(context != NULL); assert(expr != NULL); if (__builtin_expect(b == 0, 0)) { error(s, "%sdivision by zero in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } if (__builtin_expect(VALUE_MIN != 0 && a == VALUE_MIN && b == (value_t)-1, 0)) { error(s, "%sinteger overflow in division in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return a / b; } static __attribute__((unused)) value_t mod(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a, value_t b) { assert(context != NULL); assert(expr != NULL); if (__builtin_expect(b == 0, 0)) { error(s, "%smodulus by zero in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } // Is INT_MIN % -1 UD? Reading the C spec I'm not sure. if (__builtin_expect(VALUE_MIN != 0 && a == VALUE_MIN && b == (value_t)-1, 0)) { error(s, "%sinteger overflow in modulo in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return a % b; } static __attribute__((unused)) value_t negate(const char *NONNULL context, const char *rule_name, const char *NONNULL expr, const struct state *NONNULL s, value_t a) { assert(context != NULL); assert(expr != NULL); if (__builtin_expect(VALUE_MIN != 0 && a == VALUE_MIN, 0)) { error(s, "%sinteger overflow in negation in expression %s%s%s", context, expr, rule_name == NULL ? "" : " within ", rule_name == NULL ? "" : rule_name); } return -a; } /* A version of quicksort that operates on "schedules," arrays of indices that * serve as a proxy for the collection being sorted. */ static __attribute__((unused)) void sort( int (*NONNULL compare)(const struct state *s, size_t a, size_t b), size_t *NONNULL schedule, struct state *NONNULL s, size_t lower, size_t upper) { /* If we have nothing to sort, bail out. */ if (lower >= upper) { return; } /* Use Hoare's partitioning algorithm to apply quicksort. */ size_t i = lower - 1; size_t j = upper + 1; for (;;) { do { i++; assert(i >= lower && i <= upper && "out of bounds access in sort()"); } while (compare(s, schedule[i], schedule[lower]) < 0); do { j--; assert(j >= lower && j <= upper && "out of bounds access in sort()"); } while (compare(s, schedule[j], schedule[lower]) > 0); if (i >= j) { break; } /* Swap elements i and j. */ size_t temp = schedule[i]; schedule[i] = schedule[j]; schedule[j] = temp; } sort(compare, schedule, s, lower, j); sort(compare, schedule, s, j + 1, upper); } /******************************************************************************* * State queue node * * * * Queue nodes are 4K-sized, 4K-aligned linked-list nodes. They contain * * pending states and then a pointer to the next node in the queue. * ******************************************************************************/ struct queue_node { struct state *s[(4096 - sizeof(struct queue_node*)) / sizeof(struct state*)]; struct queue_node *next; }; _Static_assert(sizeof(struct queue_node) == 4096, "incorrect queue_node size calculation"); static struct queue_node *queue_node_new(void) { struct queue_node *p = NULL; int r = posix_memalign((void**)&p, sizeof(*p), sizeof(*p)); assert((r == 0 || r == ENOMEM) && "invalid alignment to posix_memalign"); if (__builtin_expect(r != 0, 0)) { oom(); } memset(p, 0, sizeof(*p)); return p; } static void queue_node_free(struct queue_node *p) { free(p); } /******************************************************************************/ /******************************************************************************* * Queue node handles * * * * These are pointers to a member-aligned address within a queue_node. The * * idea is that you can have a queue_handle_t pointing at either one of the * * elements of the `s` member or at the chained `next` pointer. Since * * queue_node pointers are always 4K-aligned, you can examine the low 12 bits * * of the queue node handle to determine which member you are pointing at. * ******************************************************************************/ typedef uintptr_t queue_handle_t; static queue_handle_t queue_handle_from_node_ptr(const struct queue_node *n) { return (queue_handle_t)n; } static struct queue_node *queue_handle_base(queue_handle_t h) { return (struct queue_node*)(h - h % sizeof(struct queue_node)); } static bool queue_handle_is_state_pptr(queue_handle_t h) { return h % sizeof(struct queue_node) < __builtin_offsetof(struct queue_node, next); } static struct state **queue_handle_to_state_pptr(queue_handle_t h) { assert(queue_handle_is_state_pptr(h) && "invalid use of queue_handle_to_state_pptr"); return (struct state**)h; } static struct queue_node **queue_handle_to_node_pptr(queue_handle_t h) { assert(!queue_handle_is_state_pptr(h) && "invalid use of queue_handle_to_node_pptr"); return (struct queue_node**)h; } static queue_handle_t queue_handle_next(queue_handle_t h) { return h + sizeof(struct state*); } /******************************************************************************/ /******************************************************************************* * Hazard pointers * * * * The idea of "hazard pointers" comes from Maged Michael, "Hazard Pointers: * * Safe Memory Reclamation for Lock-Free Objects" in TPDS 15(8) 2004. The * * basic concept is to maintain a collection of safe-to-dereference pointers. * * Before freeing a pointer, you look in this collection to see if it is in * * use and you must always add a pointer to this collection before * * dereferencing it. The finer details of how we keep this consistent and why * * the size of this collection can be statically known ahead of time are a * * little complicated, but the paper explains this in further detail. * ******************************************************************************/ /* Queue node pointers currently safe to dereference. */ static const struct queue_node *hazarded[THREADS]; /* Protect a pointer that we wish to dereference. */ static void hazard(queue_handle_t h) { /* Find the queue node this handle lies within. */ const struct queue_node *p = queue_handle_base(h); /* You can't protect the null pointer because it is invalid to dereference it. */ assert(p != NULL && "attempt to hazard an invalid pointer"); /* Each thread is only allowed a single hazarded pointer at a time. */ assert(__atomic_load_n(&hazarded[thread_id], __ATOMIC_SEQ_CST) == NULL && "hazarding multiple pointers at once"); __atomic_store_n(&hazarded[thread_id], p, __ATOMIC_SEQ_CST); } /* Drop protection on a pointer whose target we are done accessing. */ static void unhazard(queue_handle_t h) { /* Find the queue node this handle lies within. */ const struct queue_node *p __attribute__((unused)) = queue_handle_base(h); assert(p != NULL && "attempt to unhazard an invalid pointer"); assert(__atomic_load_n(&hazarded[thread_id], __ATOMIC_SEQ_CST) != NULL && "unhazarding a pointer when none are hazarded"); assert(__atomic_load_n(&hazarded[thread_id], __ATOMIC_SEQ_CST) == p && "unhazarding a pointer that differs from the one hazarded"); __atomic_store_n(&hazarded[thread_id], NULL, __ATOMIC_SEQ_CST); } /* Free a pointer or, if not possible, defer this to later. */ static void reclaim(queue_handle_t h) { /* Find the queue node this handle lies within. */ struct queue_node *p = queue_handle_base(h); assert(p != NULL && "reclaiming a null pointer"); /* The reclaimer is not allowed to be freeing something while also holding a * hazarded pointer. */ assert(__atomic_load_n(&hazarded[thread_id], __ATOMIC_SEQ_CST) == NULL && "reclaiming a pointer while holding a hazarded pointer"); /* Pointers that we failed to free initially because they were in use * (hazarded) at the time they were passed to reclaim(). * * Why are we sure we will only ever have a maximum of `THREADS - 1` pointers * outstanding? Anything passed to reclaim() is expected to be * now-unreachable, so the only outstanding references to such are threads * racing with us. Because each thread can only have one hazarded pointer at a * time, the maximum number of in use pointers right now is `THREADS - 1` * (because the current thread does not have one). * * There is an edge case where another thread (1) held a hazarded pointer we * previously tried to reclaim and thus ended up on our deferred list, then * (2) in-between that time and now dropped this reference and acquired a * hazarded pointer to `p`. This still will not exceed our count of * `THREADS - 1` as the order in which we scan the deferred list and then add * `p` to it below ensures that we will discover the originally hazarded * pointer (now no longer conflicted) and clear its slot, leaving this * available for `p`. */ static _Thread_local struct queue_node *deferred[THREADS - 1]; static const size_t DEFERRED_SIZE = sizeof(deferred) / sizeof(deferred[0]); /* First try to free any previously deferred pointers. */ for (size_t i = 0; i < DEFERRED_SIZE; i++) { if (deferred[i] != NULL) { bool conflict = false; for (size_t j = 0; j < sizeof(hazarded) / sizeof(hazarded[0]); j++) { if (j == thread_id) { /* No need to check for conflicts with ourself. */ assert(__atomic_load_n(&hazarded[j], __ATOMIC_SEQ_CST) == NULL); continue; } if (deferred[i] == __atomic_load_n(&hazarded[j], __ATOMIC_SEQ_CST)) { /* This pointer is in use by thread j. */ conflict = true; break; } } if (!conflict) { queue_node_free(deferred[i]); deferred[i] = NULL; } } } /* Now deal with the pointer we were passed. The most likely case is that no * one else is using this pointer, so try this first. */ bool conflict = false; for (size_t i = 0; i < sizeof(hazarded) / sizeof(hazarded[i]); i++) { if (i == thread_id) { /* No need to check for conflicts with ourself. */ assert(__atomic_load_n(&hazarded[i], __ATOMIC_SEQ_CST) == NULL); continue; } if (p == __atomic_load_n(&hazarded[i], __ATOMIC_SEQ_CST)) { /* Bad luck :( */ conflict = true; break; } } if (!conflict) { /* We're done! */ queue_node_free(p); return; } /* If we reached here, we need to defer this reclamation to later. */ for (size_t i = 0; i < DEFERRED_SIZE; i++) { if (deferred[i] == NULL) { deferred[i] = p; return; } } assert(!"deferred more than `THREADS` reclamations"); __builtin_unreachable(); } /******************************************************************************/ /******************************************************************************* * Atomic operations on double word values * ******************************************************************************/ #if THREADS == 1 #define atomic_read(p) (*(p)) #elif defined(__x86_64__) || defined(__i386__) /* x86-64: MOV is not guaranteed to be atomic on 128-bit naturally aligned * memory. The way to work around this is apparently the following * degenerate CMPXCHG16B. * i386: __atomic_load_n emits code calling a libatomic function that takes a * lock, making this no longer lock free. Force a CMPXCHG8B by using the * __sync built-in instead. */ #define atomic_read(p) __sync_val_compare_and_swap((p), 0, 0) #else #define atomic_read(p) __atomic_load_n((p), __ATOMIC_SEQ_CST) #endif #if THREADS == 1 #define atomic_write(p, v) do { *(p) = (v); } while (0) #elif defined(__x86_64__) || defined(__i386__) /* As explained above, we need some extra gymnastics to avoid a call to * libatomic on x86-64 and i386. */ #define atomic_write(p, v) \ do { \ __typeof__(p) _target = (p); \ __typeof__(*(p)) _expected; \ __typeof__(*(p)) _old = 0; \ __typeof__(*(p)) _new = (v); \ do { \ _expected = _old; \ _old = __sync_val_compare_and_swap(_target, _expected, _new); \ } while (_expected != _old); \ } while (0) #else #define atomic_write(p, v) __atomic_store_n((p), (v), __ATOMIC_SEQ_CST) #endif #if THREADS == 1 #define atomic_cas(p, expected, new) \ ({ \ __typeof__(p) _p = (p); \ bool _success = *_p == (expected); \ if (_success) { \ *_p = (new); \ } \ _success; \ }) #elif defined(__x86_64__) || defined(__i386__) /* Make GCC >= 7.1 emit cmpxchg on x86-64 and i386. See * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878. */ #define atomic_cas(p, expected, new) \ __sync_bool_compare_and_swap((p), (expected), (new)) #else #define atomic_cas(p, expected, new) \ __atomic_compare_exchange_n((p), &(expected), (new), false, \ __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) #endif #if THREADS == 1 #define atomic_cas_val(p, expected, new) \ ({ \ __typeof__(p) _p = (p); \ __typeof__(*(p)) _old = *_p; \ if (_old == (expected)) { \ *_p = (new); \ } \ _old; \ }) #elif defined(__x86_64__) || defined(__i386__) /* Make GCC >= 7.1 emit cmpxchg on x86-64 and i386. See * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80878. */ #define atomic_cas_val(p, expected, new) \ __sync_val_compare_and_swap((p), (expected), (new)) #else #define atomic_cas_val(p, expected, new) \ ({ \ __typeof__(expected) _expected = (expected); \ __atomic_compare_exchange_n((p), &(_expected), (new), false, \ __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); \ _expected; \ }) #endif /******************************************************************************/ /******************************************************************************* * Double pointers * * * * A scalar type that can store the value of two pointers. * ******************************************************************************/ #if __SIZEOF_POINTER__ <= 4 typedef uint64_t double_ptr_t; #elif __SIZEOF_POINTER__ <= 8 typedef unsigned __int128 double_ptr_t; #else #error "unexpected pointer size; what scalar type to use for double_ptr_t?" #endif static uintptr_t double_ptr_extract1(double_ptr_t p) { _Static_assert(sizeof(p) > sizeof(uintptr_t), "double_ptr_t is not big " "enough to fit a pointer"); uintptr_t q; memcpy(&q, &p, sizeof(q)); return q; } static uintptr_t double_ptr_extract2(double_ptr_t p) { _Static_assert(sizeof(p) >= 2 * sizeof(uintptr_t), "double_ptr_t is not big " "enough to fit two pointers"); uintptr_t q; memcpy(&q, (unsigned char*)&p + sizeof(void*), sizeof(q)); return q; } static double_ptr_t double_ptr_make(uintptr_t q1, uintptr_t q2) { double_ptr_t p = 0; _Static_assert(sizeof(p) >= 2 * sizeof(uintptr_t), "double_ptr_t is not big " "enough to fit two pointers"); memcpy(&p, &q1, sizeof(q1)); memcpy((unsigned char*)&p + sizeof(q1), &q2, sizeof(q2)); return p; } /******************************************************************************/ /******************************************************************************* * State queue * * * * The following implements a per-thread queue for pending states. The only * * supported operations are enqueueing and dequeueing states. A property we * * maintain is that all states within all queues pass the current model's * * invariants. * ******************************************************************************/ static struct { double_ptr_t ends; size_t count; } q[THREADS]; static size_t queue_enqueue(struct state *NONNULL s, size_t queue_id) { assert(queue_id < sizeof(q) / sizeof(q[0]) && "out of bounds queue access"); /* Look up the tail of the queue. */ double_ptr_t ends = atomic_read(&q[queue_id].ends); retry:; queue_handle_t tail = double_ptr_extract2(ends); if (tail == 0) { /* There's nothing currently in the queue. */ assert(double_ptr_extract1(ends) == 0 && "tail of queue 0 while head is " "non-0"); struct queue_node *n = queue_node_new(); n->s[0] = s; double_ptr_t new = double_ptr_make(queue_handle_from_node_ptr(n), queue_handle_from_node_ptr(n)); double_ptr_t old = atomic_cas_val(&q[queue_id].ends, ends, new); if (old != ends) { /* Failed. */ queue_node_free(n); ends = old; goto retry; } } else { /* The queue is non-empty, so we'll need to access the last element. */ /* Try to protect our upcoming access to the tail. */ hazard(tail); { double_ptr_t ends_check = atomic_read(&q[queue_id].ends); if (ends != ends_check) { /* Failed. Someone else modified the queue in the meantime. */ unhazard(tail); ends = ends_check; goto retry; } } /* We've now notified other threads that we're going to be accessing the * tail, so we can safely dereference its pointer. */ struct queue_node *new_node = NULL; queue_handle_t next_tail = queue_handle_next(tail); if (queue_handle_is_state_pptr(next_tail)) { /* There's an available slot in this queue node; no need to create a new * one. */ { struct state **target = queue_handle_to_state_pptr(next_tail); struct state *null = NULL; if (!__atomic_compare_exchange_n(target, &null, s, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) { /* Failed. Someone else enqueued before we could. */ unhazard(tail); goto retry; } } } else { /* There's no remaining slot in this queue node. We'll need to create a * new (empty) queue node, add our state to this one and then append this * node to the queue. */ /* Create the new node. */ new_node = queue_node_new(); new_node->s[0] = s; /* Try to update the chained pointer of the current tail to point to this * new node. */ struct queue_node **target = queue_handle_to_node_pptr(next_tail); struct queue_node *null = NULL; if (!__atomic_compare_exchange_n(target, &null, new_node, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) { /* Failed. Someone else enqueued before we could. */ queue_node_free(new_node); unhazard(tail); goto retry; } /* We now need the tail to point at our new node. */ next_tail = queue_handle_from_node_ptr(new_node); } queue_handle_t head = double_ptr_extract1(ends); double_ptr_t new = double_ptr_make(head, next_tail); /* Try to update the queue. */ { double_ptr_t old = atomic_cas_val(&q[queue_id].ends, ends, new); if (old != ends) { /* Failed. Someone else dequeued before we could finish. We know the * operation that beat us was a dequeue and not an enqueue, because by * writing to next_tail we have prevented any other enqueue from * succeeding. */ /* Undo the update of next_tail. We know this is safe (non-racy) because * no other enqueue can proceed and a dequeue will never look into * next_tail due to the way it handles the case when head == tail. */ next_tail = queue_handle_next(tail); if (queue_handle_is_state_pptr(next_tail)) { /* We previously wrote into an existing queue node. */ struct state **target = queue_handle_to_state_pptr(next_tail); struct state *temp = s; bool r __attribute__((unused)) = __atomic_compare_exchange_n(target, &temp, NULL, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); assert(r && "undo of write to next_tail failed"); } else { /* We previously wrote into a new queue node. */ struct queue_node **target = queue_handle_to_node_pptr(next_tail); struct queue_node *temp = new_node; bool r __attribute__((unused)) = __atomic_compare_exchange_n(target, &temp, NULL, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); assert(r && "undo of write to next_tail failed"); queue_node_free(new_node); } unhazard(tail); ends = old; goto retry; } } /* Success! */ unhazard(tail); } size_t count = __atomic_add_fetch(&q[queue_id].count, 1, __ATOMIC_SEQ_CST); TRACE(TC_QUEUE, "enqueued state %p into queue %zu, queue length is now %zu", s, queue_id, count); return count; } static const struct state *queue_dequeue(size_t *NONNULL queue_id) { assert(queue_id != NULL && *queue_id < sizeof(q) / sizeof(q[0]) && "out of bounds queue access"); const struct state *s = NULL; for (size_t attempts = 0; attempts < sizeof(q) / sizeof(q[0]); attempts++) { double_ptr_t ends = atomic_read(&q[*queue_id].ends); retry:; queue_handle_t head = double_ptr_extract1(ends); if (head != 0) { /* This queue is non-empty. */ /* Try to protect our upcoming accesses to the head. */ hazard(head); { double_ptr_t ends_check = atomic_read(&q[*queue_id].ends); if (ends != ends_check) { /* Failed. Someone else updated the queue. */ unhazard(head); ends = ends_check; goto retry; } } queue_handle_t tail = double_ptr_extract2(ends); double_ptr_t new; if (head == tail) { /* There is only a single element in the queue. We will need to update * both head and tail. */ new = double_ptr_make(0, 0); } else if (queue_handle_is_state_pptr(head)) { /* There are multiple elements in the queue; we can deal only with the * head. */ new = double_ptr_make(queue_handle_next(head), tail); } else { /* The head of the queue is the end of a queue node. I.e. the only thing * remaining in this queue node is the chained pointer to the next queue * node. */ /* Load the next queue node. */ struct queue_node **n = queue_handle_to_node_pptr(head); struct queue_node *new_head = __atomic_load_n(n, __ATOMIC_SEQ_CST); new = double_ptr_make(queue_handle_from_node_ptr(new_head), tail); /* Try to replace the current head with the next node. */ double_ptr_t old = atomic_cas_val(&q[*queue_id].ends, ends, new); /* Either way, now we'll need to retry, but if we succeeded we also need * to free the queue node we just removed. */ unhazard(head); if (old == ends) { /* Succeeded. */ reclaim(head); } ends = old; goto retry; } /* Try to remove the head. */ { double_ptr_t old = atomic_cas_val(&q[*queue_id].ends, ends, new); if (old != ends) { /* Failed. Someone else either enqueued or dequeued. */ unhazard(head); ends = old; goto retry; } } /* We now have either a pointer to a state or we've just removed an empty * queue node that was the last in the queue. */ if (queue_handle_is_state_pptr(head)) { struct state **st = queue_handle_to_state_pptr(head); s = *st; } unhazard(head); if (head == tail || !queue_handle_is_state_pptr(head)) { reclaim(head); } if (s == NULL) { /* Move to the next queue to try. */ *queue_id = (*queue_id + 1) % (sizeof(q) / sizeof(q[0])); continue; } size_t count = __atomic_sub_fetch(&q[*queue_id].count, 1, __ATOMIC_SEQ_CST); TRACE(TC_QUEUE, "dequeued state %p from queue %zu, queue length is now " "%zu", s, *queue_id, count); return s; } /* Move to the next queue to try. */ *queue_id = (*queue_id + 1) % (sizeof(q) / sizeof(q[0])); } return s; } /******************************************************************************/ /******************************************************************************* * Reference counted pointers * * * * These are capable of encapsulating any generic pointer (void*). Note that * * we rely on the existence of double-word atomics. On x86-64, you need to * * use compiler flag '-mcx16' to get an efficient 128-bit cmpxchg. * * * * Of these functions, only the following are thread safe: * * * * * refcounted_ptr_get * * * refcounted_ptr_peek * * * refcounted_ptr_put * * * refcounted_ptr_set * * * * The caller is expected to coordinate with other threads to exclude them * * operating on the relevant refcounted_ptr_t when using one of the other * * functions: * * * * * refcounted_ptr_shift * * * ******************************************************************************/ struct refcounted_ptr { void *ptr; size_t count; }; #if __SIZEOF_POINTER__ <= 4 typedef uint64_t refcounted_ptr_t; #elif __SIZEOF_POINTER__ <= 8 typedef unsigned __int128 refcounted_ptr_t; #else #error "unexpected pointer size; what scalar type to use for refcounted_ptr_t?" #endif _Static_assert(sizeof(struct refcounted_ptr) <= sizeof(refcounted_ptr_t), "refcounted_ptr does not fit in a refcounted_ptr_t, which we need to operate " "on it atomically"); static void refcounted_ptr_set(refcounted_ptr_t *NONNULL p, void *ptr) { #ifndef NDEBUG /* Read the current state of the pointer. */ refcounted_ptr_t old = atomic_read(p); struct refcounted_ptr p2; memcpy(&p2, &old, sizeof(old)); assert(p2.count == 0 && "overwriting a pointer source while someone still " "has a reference to this pointer"); #endif /* Set the current source pointer with no outstanding references. */ struct refcounted_ptr p3; p3.ptr = ptr; p3.count = 0; /* Commit the result. */ refcounted_ptr_t new; memcpy(&new, &p3, sizeof(p3)); atomic_write(p, new); } static void *refcounted_ptr_get(refcounted_ptr_t *NONNULL p) { refcounted_ptr_t old, new; void *ret; bool r; do { /* Read the current state of the pointer. */ old = atomic_read(p); struct refcounted_ptr p2; memcpy(&p2, &old, sizeof(old)); /* Take a reference to it. */ p2.count++; ret = p2.ptr; /* Try to commit our results. */ memcpy(&new, &p2, sizeof(new)); r = atomic_cas(p, old, new); } while (!r); return ret; } static void refcounted_ptr_put(refcounted_ptr_t *NONNULL p, void *ptr __attribute__((unused))) { refcounted_ptr_t old, new; bool r; do { /* Read the current state of the pointer. */ old = atomic_read(p); struct refcounted_ptr p2; memcpy(&p2, &old, sizeof(old)); /* Release our reference to it. */ ASSERT(p2.ptr == ptr && "releasing a reference to a pointer after someone " "has changed the pointer source"); ASSERT(p2.count > 0 && "releasing a reference to a pointer when it had no " "outstanding references"); p2.count--; /* Try to commit our results. */ memcpy(&new, &p2, sizeof(new)); r = atomic_cas(p, old, new); } while (!r); } static void *refcounted_ptr_peek(refcounted_ptr_t *NONNULL p) { /* Read out the state of the pointer. This rather unpleasant expression is * designed to emit an atomic load at a smaller granularity than the entire * refcounted_ptr structure. Because we only need the pointer -- and not the * count -- we can afford to just atomically read the first word. */ void *ptr = __atomic_load_n( (void**)((void*)p + __builtin_offsetof(struct refcounted_ptr, ptr)), __ATOMIC_SEQ_CST); return ptr; } static void refcounted_ptr_shift(refcounted_ptr_t *NONNULL current, refcounted_ptr_t *NONNULL next) { /* None of the operations in this function are performed atomically because we * assume the caller has synchronised with other threads via other means. */ /* The pointer we're about to overwrite should not be referenced. */ struct refcounted_ptr p __attribute__((unused)); memcpy(&p, current, sizeof(*current)); ASSERT(p.count == 0 && "overwriting a pointer that still has outstanding " "references"); /* Shift the next value into the current pointer. */ *current = *next; /* Blank the value we just shifted over. */ *next = 0; } /******************************************************************************/ /******************************************************************************* * Thread rendezvous support * ******************************************************************************/ static pthread_mutex_t rendezvous_lock; /* mutual exclusion mechanism for below. */ static pthread_cond_t rendezvous_cond; /* sleep mechanism for below. */ static size_t running_count = 1; /* how many threads are opted in to rendezvous? */ static size_t rendezvous_pending = 1; /* how many threads are opted in and not sleeping? */ static void rendezvous_init(void) { int r = pthread_mutex_init(&rendezvous_lock, NULL); if (__builtin_expect(r != 0, 0)) { fprintf(stderr, "pthread_mutex_init failed: %s\n", strerror(r)); exit(EXIT_FAILURE); } r = pthread_cond_init(&rendezvous_cond, NULL); if (__builtin_expect(r != 0, 0)) { fprintf(stderr, "pthread_cond_init failed: %s\n", strerror(r)); exit(EXIT_FAILURE); } } /* Call this at the start of a rendezvous point. * * This is a low level function, not expected to be directly used outside of the * context of the rendezvous implementation. * * @return True if the caller was the last to arrive and henceforth dubbed the * 'leader'. */ static bool rendezvous_arrive(void) { int r __attribute__((unused)) = pthread_mutex_lock(&rendezvous_lock); assert(r == 0); /* Take a token from the rendezvous down-counter. */ assert(rendezvous_pending > 0); rendezvous_pending--; /* If we were the last to arrive then it was our arrival that dropped the * counter to zero. */ return rendezvous_pending == 0; } /* Call this at the end of a rendezvous point. * * This is a low level function, not expected to be directly used outside of the * context of the rendezvous implementation. * * @param leader Whether the caller is the 'leader'. If you call this when you * are the 'leader' it will unblock all 'followers' at the rendezvous point. * @param action Optional code for the leader to run. */ static void rendezvous_depart(bool leader, void (*action)(void)) { int r __attribute((unused)); if (leader) { if (action != NULL) { action(); } /* Reset the counter for the next rendezvous. */ assert(rendezvous_pending == 0 && "a rendezvous point is being exited " "while some participating threads have yet to arrive"); rendezvous_pending = running_count; /* Wake up the 'followers'. */ r = pthread_cond_broadcast(&rendezvous_cond); assert(r == 0); } else { /* Wait on the 'leader' to wake us up. */ r = pthread_cond_wait(&rendezvous_cond, &rendezvous_lock); assert(r == 0); } r = pthread_mutex_unlock(&rendezvous_lock); assert(r == 0); } /* Exposed friendly function for performing a rendezvous. */ static void rendezvous(void (*action)(void)) { bool leader = rendezvous_arrive(); if (leader) { TRACE(TC_SET, "arrived at rendezvous point as leader"); } rendezvous_depart(leader, action); } /* Remove the caller from the pool of threads who participate in this * rendezvous. */ static void rendezvous_opt_out(void (*action)(void)) { retry:; /* "Arrive" at the rendezvous to decrement the count of outstanding threads. */ bool leader = rendezvous_arrive(); if (leader && running_count > 1) { /* We unfortunately opted out of this rendezvous while the remaining threads * were arriving at one and we were the last to arrive. Let's pretend we are * participating in the rendezvous and unblock them. */ rendezvous_depart(true, action); /* Re-attempt opting-out. */ goto retry; } /* Remove ourselves from the known threads. */ assert(running_count > 0); running_count--; int r __attribute__((unused)) = pthread_mutex_unlock(&rendezvous_lock); assert(r == 0); } /******************************************************************************/ /******************************************************************************* * 'Slots', an opaque wrapper around a state pointer * * * * See usage of this in the state set below for its purpose. * ******************************************************************************/ typedef uintptr_t slot_t; static __attribute__((const)) slot_t slot_empty(void) { return 0; } static __attribute__((const)) bool slot_is_empty(slot_t s) { return s == slot_empty(); } static __attribute__((const)) slot_t slot_tombstone(void) { static const slot_t TOMBSTONE = ~(slot_t)0; return TOMBSTONE; } static __attribute__((const)) bool slot_is_tombstone(slot_t s) { return s == slot_tombstone(); } static struct state *slot_to_state(slot_t s) { ASSERT(!slot_is_empty(s)); ASSERT(!slot_is_tombstone(s)); return (struct state*)s; } static slot_t state_to_slot(const struct state *s) { return (slot_t)s; } /******************************************************************************/ /******************************************************************************* * State set * * * * The following implementation provides a set for storing the seen states. * * There is no support for removing elements, only thread-safe insertion of * * elements. * ******************************************************************************/ enum { INITIAL_SET_SIZE_EXPONENT = sizeof(unsigned long long) * 8 - 1 - __builtin_clzll(SET_CAPACITY / sizeof(struct state*) / sizeof(struct state)) }; struct set { slot_t *bucket; size_t size_exponent; }; /* Some utility functions for dealing with exponents. */ static size_t set_size(const struct set *NONNULL set) { return ((size_t)1) << set->size_exponent; } static size_t set_index(const struct set *NONNULL set, size_t index) { return index & (set_size(set) - 1); } /* The states we have encountered. This collection will only ever grow while * checking the model. Note that we have a global reference-counted pointer and * a local bare pointer. See below for an explanation. */ static refcounted_ptr_t global_seen; static _Thread_local struct set *local_seen; /* Number of elements in the global set (i.e. occupancy). */ static size_t seen_count; /* The "next" 'global_seen' value. See below for an explanation. */ static refcounted_ptr_t next_global_seen; /* Now the explanation I teased... When the set capacity exceeds a threshold * (see 'set_expand' related logic below) it is expanded and the reference * tracking within the 'refcounted_ptr_t's comes in to play. We need to allocate * a new seen set ('next_global_seen'), copy over all the elements (done in * 'set_migrate'), and then "shift" the new set to become the current set. The * role of the reference counts of both 'global_seen' and 'next_global_seen' in * all of this is to detect when the last thread releases its reference to the * old seen set and hence can deallocate it. */ /* The next chunk to migrate from the old set to the new set. What exactly a * "chunk" is is covered in 'set_migrate'. */ static size_t next_migration; /* A mechanism for synchronisation in 'set_expand'. */ static pthread_mutex_t set_expand_mutex; static void set_expand_lock(void) { if (THREADS > 1) { int r __attribute__((unused)) = pthread_mutex_lock(&set_expand_mutex); ASSERT(r == 0); } } static void set_expand_unlock(void) { if (THREADS > 1) { int r __attribute__((unused)) = pthread_mutex_unlock(&set_expand_mutex); ASSERT(r == 0); } } static void set_init(void) { if (THREADS > 1) { int r = pthread_mutex_init(&set_expand_mutex, NULL); if (__builtin_expect(r < 0, 0)) { fprintf(stderr, "pthread_mutex_init failed: %s\n", strerror(r)); exit(EXIT_FAILURE); } } /* Allocate the set we'll store seen states in at some conservative initial * size. */ struct set *set = xmalloc(sizeof(*set)); set->size_exponent = INITIAL_SET_SIZE_EXPONENT; set->bucket = xcalloc(set_size(set), sizeof(set->bucket[0])); /* Stash this somewhere for threads to later retrieve it from. Note that we * initialize its reference count to zero as we (the setup logic) are not * using it beyond this function. */ refcounted_ptr_set(&global_seen, set); } static void set_thread_init(void) { /* Take a local reference to the global seen set. */ local_seen = refcounted_ptr_get(&global_seen); } static void set_update(void) { /* Guard against the case where we've been called from exit_with() and we're * not finishing a migration, but just opting out of the rendezvous protocol. */ if (refcounted_ptr_peek(&next_global_seen) != NULL) { /* Clean up the old set. Note that we are using a pointer we have already * given up our reference count to here, but we rely on the caller to ensure * this access is safe. */ free(local_seen->bucket); free(local_seen); /* Reset migration state for the next time we expand the set. */ next_migration = 0; /* Update the global pointer to the new set. We know all the above * migrations have completed and no one needs the old set. */ refcounted_ptr_shift(&global_seen, &next_global_seen); } } static void set_migrate(void) { TRACE(TC_SET, "assisting in set migration..."); /* Size of a migration chunk. Threads in this function grab a chunk at a time * to migrate. */ enum { CHUNK_SIZE = 4096 / sizeof(local_seen->bucket[0]) /* slots */ }; /* Take a pointer to the target set for the migration. */ struct set *next = refcounted_ptr_get(&next_global_seen); for (;;) { size_t chunk = __atomic_fetch_add(&next_migration, 1, __ATOMIC_SEQ_CST); size_t start = chunk * CHUNK_SIZE; size_t end = start + CHUNK_SIZE; /* Bail out if we've finished migrating all of the set. */ if (start >= set_size(local_seen)) { break; } // TODO: The following algorithm assumes insertions can collide. That is, it // operates atomically on slots because another thread could be migrating // and also targeting the same slot. If we were to more closely wick to the // Maier design this would not be required. for (size_t i = start; i < end; i++) { /* retrieve the slot element and mark it as migrated */ slot_t s = __atomic_exchange_n(&local_seen->bucket[i], slot_tombstone(), __ATOMIC_SEQ_CST); ASSERT(!slot_is_tombstone(s) && "attempted double slot migration"); /* If the current slot contained a state, rehash it and insert it into the * new set. Note we don't need to do any state comparisons because we know * everything in the old set is unique. */ if (!slot_is_empty(s)) { size_t index = set_index(next, state_hash(slot_to_state(s))); /* insert and shuffle any colliding entries one along */ for (size_t j = index; !slot_is_empty(s); j = set_index(next, j + 1)) { s = __atomic_exchange_n(&next->bucket[j], s, __ATOMIC_SEQ_CST); } } } } /* Release our reference to the old set now we're done with it. */ refcounted_ptr_put(&global_seen, local_seen); /* Now we need to make sure all the threads get to this point before any one * thread leaves. The purpose of this is to guarantee we only ever have at * most two seen sets "in flight". Without this rendezvous, one thread could * race ahead, fill the new set, and then decide to expand again while some * are still working on the old set. It's possible to make such a scheme work * but the synchronisation requirements just seem too complicated. */ rendezvous(set_update); /* We're now ready to resume model checking. Note that we already have a * (reference counted) pointer to the now-current global seen set, so we don't * need to take a fresh reference to it. */ local_seen = next; } static void set_expand(void) { /* Using double-checked locking, we look to see if someone else has already * started expanding the set. We do this by first checking before acquiring * the set mutex, and then later again checking after we've acquired the * mutex. The idea here is that, with multiple threads, you'll frequently find * someone else beat you to expansion and you can jump straight to helping * them with migration without having the expense of acquiring the set mutex. */ if (THREADS > 1 && refcounted_ptr_peek(&next_global_seen) != NULL) { /* Someone else already expanded it. Join them in the migration effort. */ TRACE(TC_SET, "attempted expansion failed because another thread got there " "first"); set_migrate(); return; } set_expand_lock(); /* Check again, as described above. */ if (THREADS > 1 && refcounted_ptr_peek(&next_global_seen) != NULL) { set_expand_unlock(); TRACE(TC_SET, "attempted expansion failed because another thread got there " "first"); set_migrate(); return; } TRACE(TC_SET, "expanding set from %zu slots to %zu slots...", (((size_t)1) << local_seen->size_exponent) / sizeof(slot_t), (((size_t)1) << (local_seen->size_exponent + 1)) / sizeof(slot_t)); /* Create a set of double the size. */ struct set *set = xmalloc(sizeof(*set)); set->size_exponent = local_seen->size_exponent + 1; set->bucket = xcalloc(set_size(set), sizeof(set->bucket[0])); /* Advertise this as the newly expanded global set. */ refcounted_ptr_set(&next_global_seen, set); /* We now need to migrate all slots from the old set to the new one, but we * can do this multithreaded. */ set_expand_unlock(); set_migrate(); } static bool set_insert(struct state *NONNULL s, size_t *NONNULL count) { restart:; if (__atomic_load_n(&seen_count, __ATOMIC_SEQ_CST) * 100 / set_size(local_seen) >= SET_EXPAND_THRESHOLD) set_expand(); size_t index = set_index(local_seen, state_hash(s)); size_t attempts = 0; for (size_t i = index; attempts < set_size(local_seen); i = set_index(local_seen, i + 1)) { /* Guess that the current slot is empty and try to insert here. */ slot_t c = slot_empty(); if (__atomic_compare_exchange_n(&local_seen->bucket[i], &c, state_to_slot(s), false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) { /* Success */ *count = __atomic_add_fetch(&seen_count, 1, __ATOMIC_SEQ_CST); TRACE(TC_SET, "added state %p, set size is now %zu", s, *count); /* The maximum possible size of the seen state set should be constrained * by the number of possible states based on how many bits we are using to * represent the state data. */ if (STATE_SIZE_BITS < sizeof(size_t) * CHAR_BIT) { assert(*count <= ((size_t)1) << STATE_SIZE_BITS && "seen set size " "exceeds total possible number of states"); } /* Update statistics if `--trace memory_usage` is in effect. Note that we * do this here (when a state is being added to the seen set) rather than * when the state was originally allocated to ensure that the final * allocation figures do not include transient states that we allocated * and then discarded as duplicates. */ size_t depth = 0; #if BOUND > 0 depth = (size_t)state_bound_get(s); #endif register_allocation(depth); return true; } if (slot_is_tombstone(c)) { /* This slot has been migrated. We need to rendezvous with other migrating * threads and restart our insertion attempt on the newly expanded set. */ set_migrate(); goto restart; } /* If we find this already in the set, we're done. */ if (state_eq(s, slot_to_state(c))) { TRACE(TC_SET, "skipped adding state %p that was already in set", s); return false; } attempts++; } /* If we reach here, the set is full. Expand it and retry the insertion. */ set_expand(); return set_insert(s, count); } /* Find an existing element in the set. * * Why would you ever want to do this? If you already have the state, why do you * want to find a copy of it? The answer is for liveness information. When * checking liveness properties, a duplicate of your current state that is * already contained in the state set might know some of the liveness properties * are satisfied that your current state considers unknown. */ static __attribute__((unused)) const struct state *set_find( const struct state *NONNULL s) { assert(s != NULL); size_t index = set_index(local_seen, state_hash(s)); size_t attempts = 0; for (size_t i = index; attempts < set_size(local_seen); i = set_index(local_seen, i + 1)) { slot_t slot = __atomic_load_n(&local_seen->bucket[i], __ATOMIC_SEQ_CST); /* This function is only expected to be called during the final liveness * scan, in which all threads participate. So we should never encounter a * set undergoing migration. */ ASSERT(!slot_is_tombstone(slot) && "tombstone encountered during final phase"); if (slot_is_empty(slot)) { /* reached the end of the linear block in which this state could lie */ break; } const struct state *n = slot_to_state(slot); ASSERT(n != NULL && "null pointer stored in state set"); if (state_eq(s, n)) { /* found */ return n; } attempts++; } /* not found */ return NULL; } /******************************************************************************/ static time_t START_TIME; static unsigned long long gettime() { return (unsigned long long)(time(NULL) - START_TIME); } #if LIVENESS_COUNT > 0 /* Set one of the liveness bits (i.e. mark the matching property as 'hit') in a * state and all its predecessors. */ static __attribute__((unused)) void mark_liveness(struct state *NONNULL s, size_t index, bool shared) { assert(s != NULL); ASSERT(index < sizeof(s->liveness) * CHAR_BIT && "out of range liveness write"); size_t word_index = index / (sizeof(s->liveness[0]) * CHAR_BIT); size_t bit_index = index % (sizeof(s->liveness[0]) * CHAR_BIT); uintptr_t previous_value; uintptr_t *target = &s->liveness[word_index]; uintptr_t mask = ((uintptr_t)1) << bit_index; if (shared) { /* If this state is shared (accessible by other threads) we need to operate * on its liveness data atomically. */ previous_value = __atomic_fetch_or(target, mask, __ATOMIC_SEQ_CST); } else { /* Otherwise we can use a cheaper ordinary OR. */ previous_value = *target; *target |= mask; } /* The following looks a little odd. Why do we only initialise this pointer * properly when we have liveness properties? The answer is that the * `previous` pointer in the state struct only exists when we have liveness * properties, so the access would cause a compile error if we did it * unconditionally. We leave the rest of this function visible to the compiler * even when we have no liveness properties to confirm it is syntactically * valid. */ struct state *previous = NULL; /* Cheat a little and cast away the constness of the previous state for which * we may need to update liveness data. */ previous = state_drop_const(state_previous_get(s)); /* If the given bit was already set, we know all the predecessors of this * state have already had their corresponding bit marked. However, if it was * not we now need to recurse to mark them. Note that we assume any * predecessors of this state are globally visible and hence shared. The * recursion depth here can be indeterminately deep, but we assume the * compiler can tail-optimise this call. */ if ((previous_value & mask) != mask && previous != NULL) { mark_liveness(previous, index, true); } } /* number of unknown liveness properties for a given state */ static unsigned long unknown_liveness(const struct state *NONNULL s) { assert(s != NULL); unsigned long unknown = 0; for (size_t i = 0; i < sizeof(s->liveness) / sizeof(s->liveness[0]); i++) { uintptr_t word = __atomic_load_n(&s->liveness[i], __ATOMIC_SEQ_CST); for (size_t j = 0; j < sizeof(s->liveness[0]) * CHAR_BIT; j++) { if (i * sizeof(s->liveness[0]) * CHAR_BIT + j >= LIVENESS_COUNT) { break; } if (!((word >> j) & 0x1)) { unknown++; } } } return unknown; } /* Learn new liveness information about the state `s` from its successor. Note * that typically `state_previous_get(successor) != s` because `successor` is * actually one of the de-duped aliases of the original successor to `s`. * * @param s State to learn information about * @param successor Successor to s * @return Number of new liveness information facts learnt */ static unsigned long learn_liveness(struct state *NONNULL s, const struct state *NONNULL successor) { assert(s != NULL); assert(successor != NULL); unsigned long new_info = 0; for (size_t i = 0; i < sizeof(s->liveness) / sizeof(s->liveness[0]); i++) { uintptr_t word_src = __atomic_load_n(&successor->liveness[i], __ATOMIC_SEQ_CST); uintptr_t word_dst = __atomic_load_n(&s->liveness[i], __ATOMIC_SEQ_CST); for (size_t j = 0; j < sizeof(s->liveness[0]) * CHAR_BIT; j++) { if (i * sizeof(s->liveness[0]) * CHAR_BIT + j >= LIVENESS_COUNT) { break; } bool live_src = !!((word_src >> j) & 0x1); bool live_dst = !!((word_dst >> j) & 0x1); if (!live_dst && live_src) { mark_liveness(s, i * sizeof(s->liveness[0]) * CHAR_BIT + j, true); new_info++; } } } return new_info; } #endif /* Prototypes for generated functions. */ static void init(void); static _Noreturn void explore(void); #if LIVENESS_COUNT > 0 static void check_liveness_final(void); static unsigned long check_liveness_summarise(void); #endif static int exit_with(int status) { /* Opt out of the thread-wide rendezvous protocol. */ refcounted_ptr_put(&global_seen, local_seen); rendezvous_opt_out(set_update); local_seen = NULL; /* Make fired rule count visible globally. */ rules_fired[thread_id] = rules_fired_local; if (thread_id == 0) { /* We are the initial thread. Wait on the others before exiting. */ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif for (size_t i = 0; phase == RUN && i < sizeof(threads) / sizeof(threads[0]); i++) { #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif void *ret; int r = pthread_join(threads[i], &ret); if (__builtin_expect(r != 0, 0)) { print_lock(); fprintf(stderr, "failed to join thread: %s\n", strerror(r)); print_unlock(); continue; } status |= (int)(intptr_t)ret; } /* We're now single-threaded again. */ /* Reacquire a pointer to the seen set. Note that this may not be the same * value as what we previously had in local_seen because the other threads * may have expanded and migrated the seen set in the meantime. */ local_seen = refcounted_ptr_get(&global_seen); if (error_count == 0) { /* If we didn't see any other errors, print cover information. */ #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif for (size_t i = 0; i < sizeof(covers) / sizeof(covers[0]); i++) { #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif if (MACHINE_READABLE_OUTPUT) { printf("\n", covers[i]); } if (covers[i] == 0) { if (!MACHINE_READABLE_OUTPUT) { printf("\t%s%scover \"%s\" not hit%s\n", red(), bold(), COVER_MESSAGES[i], reset()); } error_count++; status = EXIT_FAILURE; } else if (!MACHINE_READABLE_OUTPUT) { printf("\t%s%scover \"%s\" hit %" PRIuMAX " times%s\n", green(), bold(), COVER_MESSAGES[i], covers[i], reset()); } } } #if LIVENESS_COUNT > 0 /* If we have liveness properties to assess and have seen no previous * errors, do a final check of them now. */ if (error_count == 0) { check_liveness_final(); unsigned long failed = check_liveness_summarise(); if (failed > 0) { error_count += failed; status = EXIT_FAILURE; } } #endif if (!MACHINE_READABLE_OUTPUT) { printf("\n" "==========================================================================\n" "\n" "Status:\n" "\n"); if (error_count == 0) { printf("\t%s%sNo error found.%s\n", green(), bold(), reset()); } else { printf("\t%s%s%lu error(s) found.%s\n", red(), bold(), error_count, reset()); } printf("\n"); } /* Calculate the total number of rules fired. */ uintmax_t fire_count = 0; for (size_t i = 0; i < sizeof(rules_fired) / sizeof(rules_fired[0]); i++) { fire_count += rules_fired[i]; } /* Paranoid check that we didn't miscount during set insertions/expansions. */ #ifndef NDEBUG size_t count = 0; for (size_t i = 0; i < set_size(local_seen); i++) { if (!slot_is_empty(local_seen->bucket[i])) { count++; } } #endif assert(count == seen_count && "seen set count is inconsistent at exit"); if (MACHINE_READABLE_OUTPUT) { printf("\n", seen_count, fire_count, error_count, gettime()); printf("\n"); } else { printf("State Space Explored:\n" "\n" "\t%zu states, %" PRIuMAX " rules fired in %llus.\n", seen_count, fire_count, gettime()); } /* print memory usage statistics if `--trace memory_usage` is in effect */ print_allocation_summary(); exit(status); } else { pthread_exit((void*)(intptr_t)status); } } static void *thread_main(void *arg) { /* Initialize (thread-local) thread identifier. */ thread_id = (size_t)(uintptr_t)arg; set_thread_init(); explore(); } static void start_secondary_threads(void) { /* XXX: Kind of hacky. We've left the rendezvous down-counter at 1 until now * in case we triggered a rendezvous before starting the other threads (it * *can* happen). We bump it immediately -- i.e. before starting *any* of the * secondary threads -- because any of them could race us and trigger a * rendezvous before we exit the below loop and they need to know about the * thundering herd bearing down on them. It is safe to do this without holding * rendezvous_lock because we are still single threaded at this point. */ assert(running_count == 1); running_count = THREADS; assert(rendezvous_pending == 1); rendezvous_pending = THREADS; #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wtautological-unsigned-zero-compare" #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" #endif for (size_t i = 0; i < sizeof(threads) / sizeof(threads[0]); i++) { #ifdef __clang__ #pragma clang diagnostic pop #elif defined(__GNUC__) #pragma GCC diagnostic pop #endif int r = pthread_create(&threads[i], NULL, thread_main, (void*)(uintptr_t)(i + 1)); if (__builtin_expect(r != 0, 0)) { fprintf(stderr, "pthread_create failed: %s\n", strerror(r)); exit(EXIT_FAILURE); } } } int main(void) { if (COLOR == AUTO) istty = isatty(STDOUT_FILENO) != 0; /* We don't need to read anything from stdin, so discard it. */ (void)fclose(stdin); sandbox(); if (MACHINE_READABLE_OUTPUT) { printf("\n" "\n" "\n", (size_t)STATE_SIZE_BITS, (size_t)STATE_SIZE_BYTES, ((size_t)1) << INITIAL_SET_SIZE_EXPONENT); } else { printf("Memory usage:\n" "\n" "\t* The size of each state is %zu bits (rounded up to %zu bytes).\n" "\t* The size of the hash table is %zu slots.\n" "\n", (size_t)STATE_SIZE_BITS, (size_t)STATE_SIZE_BYTES, ((size_t)1) << INITIAL_SET_SIZE_EXPONENT); } #ifndef NDEBUG state_print_field_offsets(); #endif START_TIME = time(NULL); rendezvous_init(); set_init(); set_thread_init(); init(); if (!MACHINE_READABLE_OUTPUT) { printf("Progress Report:\n\n"); } explore(); } rumur-2020.02.17/rumur/resources/includes.c000066400000000000000000000015141362265074000204230ustar00rootroot00000000000000/* Setting a POSIX version on FreeBSD causes other functions to become hidden. */ #ifndef __FreeBSD__ #define _POSIX_C_SOURCE 200809L #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #endif #ifdef __APPLE__ #include #elif defined(__FreeBSD__) #include #elif defined(__linux__) #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) #include #include #include #include #include #include #endif #endif rumur-2020.02.17/rumur/src/000077500000000000000000000000001362265074000152255ustar00rootroot00000000000000rumur-2020.02.17/rumur/src/ValueType.cc000066400000000000000000000142561362265074000174620ustar00rootroot00000000000000#include #include #include #include #include #include "log.h" #include #include #include #include #include #include "ValueType.h" #include using namespace rumur; namespace { class BoundsFinder : public ConstTraversal { public: mpz_class min = 0; mpz_class max = 0; private: void increase_max(const mpz_class &new_value, const std::string &cause) { *debug << "increasing maximum numerical bound to " << new_value << " due to \"" << cause << "\"\n"; max = new_value; } void decrease_min(const mpz_class &new_value, const std::string &cause) { *debug << "decreasing minimum numerical bound to " << new_value << " due to \"" << cause << "\"\n"; min = new_value; } public: void visit_enum(const Enum &n) final { if (n.members.size() > max) increase_max(n.members.size(), n.to_string()); } /* We explicitly handle negative expressions because the Rumur AST sees them * as something compound, but users tend to think of them as an atom. For * example, if a user writes "-1" they expect the automatically derived type * will be able to contain -1. If we did not handle Negate specifically, this * analysis would only look at the inner "1" and conclude a narrower range * than was intuitive to the user. */ void visit_negative(const Negative &n) final { if (auto l = dynamic_cast(&*n.rhs)) { mpz_class v = -l->value; if (v < min) decrease_min(v, n.to_string()); if (v > max) increase_max(v, n.to_string()); } dispatch(*n.rhs); } void visit_number(const Number &n) final { if (n.value < min) decrease_min(n.value, n.to_string()); if (n.value > max) increase_max(n.value, n.to_string()); } // we override visit_quantifier in order to also descend into the quantifier’s // decl that the generic traversal logic assumes you do not want to do void visit_quantifier(const Quantifier &n) final { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); dispatch(*n.decl); } void visit_range(const Range &n) final { if (n.min->constant()) { mpz_class m = n.min->constant_fold(); if (m < min) decrease_min(m, n.min->to_string()); if (m > max) increase_max(m, n.min->to_string()); } if (n.max->constant()) { mpz_class m = n.max->constant_fold(); if (m < min) decrease_min(m, n.max->to_string()); if (m > max) increase_max(m, n.max->to_string()); } } void visit_scalarset(const Scalarset &n) final { if (n.bound->constant()) { mpz_class m = n.bound->constant_fold(); if (m < min) decrease_min(m, n.bound->to_string()); if (m > max) increase_max(m, n.bound->to_string()); } } }; } static const std::unordered_map types = { { "int8_t", { "int_fast8_t", "INT_FAST8_MIN", "INT_FAST8_MAX", "INT8_C", "PRIdFAST8", mpz_class(std::to_string(INT8_MIN)), mpz_class(std::to_string(INT8_MAX)) } }, { "uint8_t", { "uint_fast8_t", "((uint8_t)0)", "UINT_FAST8_MAX", "UINT8_C", "PRIuFAST8", 0, mpz_class(std::to_string(UINT8_MAX)) } }, { "int16_t", { "int_fast16_t", "INT_FAST16_MIN", "INT_FAST16_MAX", "INT16_C", "PRIdFAST16", mpz_class(std::to_string(INT16_MIN)), mpz_class(std::to_string(INT16_MAX)) } }, { "uint16_t", { "uint_fast16_t", "((uint16_t)0)", "UINT_FAST16_MAX", "UINT16_C", "PRIuFAST16", 0, mpz_class(std::to_string(UINT16_MAX)) } }, { "int32_t", { "int_fast32_t", "INT_FAST32_MIN", "INT_FAST32_MAX", "INT32_C", "PRIdFAST32", mpz_class(std::to_string(INT32_MIN)), mpz_class(std::to_string(INT32_MAX)) } }, { "uint32_t", { "uint_fast32_t", "((uint32_t)0)", "UINT_FAST32_MAX", "UINT32_C", "PRIuFAST32", 0, mpz_class(std::to_string(UINT32_MAX)) } }, { "int64_t", { "int_fast64_t", "INT_FAST64_MIN", "INT_FAST64_MAX", "INT64_C", "PRIdFAST64", mpz_class(std::to_string(INT64_MIN)), mpz_class(std::to_string(INT64_MAX)) } }, { "uint64_t", { "uint_fast64_t", "((uint64_t)0)", "UINT_FAST64_MAX", "UINT64_C", "PRIuFAST64", 0, mpz_class(std::to_string(UINT64_MAX)) } }, }; // a list of the types sorted by estimated expense static const std::vector TYPES = { "uint8_t", "int8_t", "uint16_t", "int16_t", "uint32_t", "int32_t", "uint64_t", "int64_t" }; // find an unsigned type that can contain the given range shifted to be 1-based static const ValueType raw_type(const mpz_class &min, const mpz_class &max) { assert(min <= max && "reversed bounds in raw_type()"); mpz_class limit = max - min + 1; for (const std::string &t : TYPES) { const ValueType &vt = types.at(t); // skip signed types if (vt.min < 0) continue; if (vt.max >= limit) return vt; } throw std::runtime_error("no supported unsigned type is wide enough to " "contain enough values to cover the range [" + min.get_str() + ", " + max.get_str() + "]"); } std::pair get_value_type(const std::string &name, const Model &m) { if (name == "auto") { // find least and greatest numerical values needed for this model BoundsFinder bf; bf.dispatch(m); // find the first type that satisfies these for (const std::string &t : TYPES) { *debug << "considering type " << t << "...\n"; const ValueType &vt = types.at(t); if (vt.min <= bf.min && vt.max >= bf.max) { *info << "using numerical type " << t << " as value type\n"; return { vt, raw_type(bf.min, bf.max) }; } } throw std::runtime_error("model's numerical bounds are [" + bf.min.get_str() + ", " + bf.max.get_str() + "] which no supported type contains"); } auto it = types.find(name); if (it == types.end()) throw std::runtime_error("unknown type " + name); const ValueType &vt = it->second; return { vt, raw_type(vt.min, vt.max) }; } rumur-2020.02.17/rumur/src/ValueType.h000066400000000000000000000013051362265074000173130ustar00rootroot00000000000000#pragma once #include #include #include #include #include // abstraction over the type used to represent scalar values during checking struct ValueType { std::string c_type; // C symbol that names the type std::string int_min; // equivalent of INT_MIN std::string int_max; // equivalent of INT_MAX std::string int_c; // equivalent of INT_C std::string pri; // equivalent of PRId64 mpz_class min; // minimum value that can be represented in this type mpz_class max; // maximum value that can be represented in this type }; std::pair get_value_type(const std::string &name, const rumur::Model &m); rumur-2020.02.17/rumur/src/assume-statements-count.cc000066400000000000000000000012311362265074000223410ustar00rootroot00000000000000#include #include "assume-statements-count.h" #include using namespace rumur; unsigned long assume_statements_count(const Model &model) { // define a traversal for counting assume statements class AssumeCounter : public ConstTraversal { public: unsigned long count = 0; void visit_propertystmt(const PropertyStmt &n) final { if (n.property.category == Property::ASSUMPTION) count++; // no need to descend into children } virtual ~AssumeCounter() = default; }; // use the counter to find how many assume statments we have AssumeCounter ac; ac.dispatch(model); return ac.count; } rumur-2020.02.17/rumur/src/assume-statements-count.h000066400000000000000000000002621362265074000222060ustar00rootroot00000000000000#pragma once #include #include // find the number of assume statements in the model unsigned long assume_statements_count(const rumur::Model &model); rumur-2020.02.17/rumur/src/environ.cc000066400000000000000000000006521362265074000172170ustar00rootroot00000000000000#include #include "environ.h" #ifdef __APPLE__ #include #endif char **get_environ(void) { #ifdef __APPLE__ // on macOS, environ is not directly accessible return *_NSGetEnviron(); #else /* some platforms fail to expose environ in a header (e.g. FreeBSD), so * declare it ourselves and assume it will be available when linking */ extern char **environ; return environ; #endif } rumur-2020.02.17/rumur/src/environ.h000066400000000000000000000001211362265074000170500ustar00rootroot00000000000000#pragma once // get a pointer to the environ variable char **get_environ(void); rumur-2020.02.17/rumur/src/generate-allocations.cc000066400000000000000000000020701362265074000216330ustar00rootroot00000000000000#include #include "generate.h" #include #include #include #include using namespace rumur; namespace { class Generator : public ConstTraversal { private: std::ostream *out; public: Generator(std::ostream &o): out(&o) { } void visit_functioncall(const FunctionCall &n) final { if (n.function == nullptr) throw Error("function call to unresolved target " + n.name, n.loc); define_backing_mem(n.unique_id, n.function->return_type.get()); for (auto &a : n.arguments) dispatch(*a); } virtual ~Generator() = default; private: void define_backing_mem(size_t id, const TypeExpr *t) { if (t != nullptr && !t->is_simple()) *out << " uint8_t ret" << id << "[BITS_TO_BYTES(" << t->width() << ")];\n"; } }; } void generate_allocations(std::ostream &out, const Stmt &stmt) { Generator g(out); g.dispatch(stmt); } void generate_allocations(std::ostream &out, const std::vector> &stmts) { for (auto &s : stmts) generate_allocations(out, *s); } rumur-2020.02.17/rumur/src/generate-cover-array.cc000066400000000000000000000035201362265074000215560ustar00rootroot00000000000000#include "generate.h" #include #include #include "utils.h" using namespace rumur; namespace { class CoverAccumulator : public ConstTraversal { private: std::ostream *out; size_t count = 0; public: CoverAccumulator(std::ostream &o): out(&o) { } void visit_property(const Property &n) final { if (n.category == Property::COVER) { *out << " COVER_" << n.unique_id << " = " << count << ",\n"; count++; } } size_t get_count() const { return count; } }; class MessageGenerator : public ConstTraversal { private: std::ostream *out; public: MessageGenerator(std::ostream &o): out(&o) { } void visit_propertyrule(const PropertyRule &n) final { if (n.property.category == Property::COVER) { if (n.name == "") { // No associated message. Just use the expression itself. *out << " \"" << to_C_string(*n.property.expr) << "\",\n"; } else { *out << " \"" << escape(n.name) << "\",\n"; } } } void visit_propertystmt(const PropertyStmt &n) final { if (n.property.category == Property::COVER) { if (n.message == "") { // No associated message. Just use the expression itself. *out << " \"" << to_C_string(*n.property.expr) << "\",\n"; } else { *out << " \"" << escape(n.message) << "\",\n"; } } } }; } void generate_cover_array(std::ostream &out, const Model &model) { out << "enum {\n"; CoverAccumulator ca(out); ca.dispatch(model); out << " /* Dummy entry in case the above generated list is empty to avoid an empty enum. */\n" << " COVER_INVALID = -1,\n" << "};\n\n"; out << "static const char *COVER_MESSAGES[] = {\n"; MessageGenerator mg(out); mg.dispatch(model); out << "};\n\n"; out << "static uintmax_t covers[" << ca.get_count() << "];\n\n"; } rumur-2020.02.17/rumur/src/generate-decl.cc000066400000000000000000000032051362265074000202330ustar00rootroot00000000000000#include #include #include "generate.h" #include #include using namespace rumur; void generate_decl(std::ostream &out, const Decl &d) { if (auto a = dynamic_cast(&d)) { const Ptr t = a->value->type(); if (t->is_simple() && !a->value->is_lvalue()) { out << "value_t"; } else { out << "struct handle"; } out << " ru_" << a->name << " __attribute__((unused)) = "; if (a->value->is_lvalue()) { generate_lvalue(out, *a->value); } else { generate_rvalue(out, *a->value); } return; } if (auto c = dynamic_cast(&d)) { assert(c->get_type()->is_simple() && "complex const decl"); out << "static const value_t ru_" << c->name << " __attribute__((unused)) " << "= VALUE_C(" << c->value->constant_fold() << ")"; return; } if (auto v = dynamic_cast(&d)) { // If this has a valid offset, it's a state variable. if (v->offset >= 0) { out << "const struct handle ru_" << v->name << " __attribute__((unused)) " << "= state_handle(s, " << v->offset << "ull, " << v->type->width() << "ull)"; // Otherwise we need to allocate backing memory for it. } else { out << "uint8_t _ru_" << v->name << "[BITS_TO_BYTES(" << v->type->width() << ")] = { 0 };\n" << " const struct handle ru_" << v->name << " __attribute__((unused)) " << "= { .base = _ru_" << v->name << ", .offset = 0ul, .width = " << v->type->width() << "ull }"; } return; } assert(!"unexpected TypeDecl in generate_decl"); } rumur-2020.02.17/rumur/src/generate-expr.cc000066400000000000000000000426741362265074000203170ustar00rootroot00000000000000#include #include #include "generate.h" #include #include #include #include #include #include "utils.h" using namespace rumur; namespace { class Generator : public ConstExprTraversal { private: std::ostream *out; bool lvalue; public: Generator(std::ostream &o, bool lvalue_): out(&o), lvalue(lvalue_) { } // Make emitting an rvalue more concise below Generator &operator<<(const Expr &e) { Generator g(*out, false); g.dispatch(e); return *this; } Generator &operator<<(const std::string &s) { *out << s; return *this; } void visit_add(const Add &n) final { if (lvalue) invalid(n); *this << "add(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.lhs << ", " << *n.rhs << ")"; } void visit_and(const And &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " && " << *n.rhs << ")"; } void visit_div(const Div &n) final { if (lvalue) invalid(n); *this << "divide(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.lhs << ", " << *n.rhs << ")"; } void visit_element(const Element &n) final { if (lvalue && !n.is_lvalue()) invalid(n); // First, determine the width of the array's elements const Ptr t1 = n.array->type(); assert(t1 != nullptr && "array with invalid type"); const Ptr t2 = t1->resolve(); assert(t2 != nullptr && "array with invalid type"); auto a = dynamic_cast(*t2); mpz_class element_width = a.element_type->width(); // Second, determine the minimum and maximum values of the array's index type const Ptr t3 = a.index_type->resolve(); assert(t3 != nullptr && "array with invalid index type"); mpz_class min, max; if (auto r = dynamic_cast(t3.get())) { min = r->min->constant_fold(); max = r->max->constant_fold(); } else if (auto e = dynamic_cast(t3.get())) { min = 0; max = e->count() - 1; } else if (auto s = dynamic_cast(t3.get())) { min = 0; max = s->bound->constant_fold() - 1; } else { assert(false && "array with invalid index type"); } if (!lvalue && a.element_type->is_simple()) { const std::string lb = a.element_type->lower_bound(); const std::string ub = a.element_type->upper_bound(); *out << "handle_read(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << lb << ", " << ub << ", "; } *out << "handle_index(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << element_width << "ull, VALUE_C(" << min << "), VALUE_C(" << max << "), "; if (lvalue) { generate_lvalue(*out, *n.array); } else { generate_rvalue(*out, *n.array); } *this << ", " << *n.index << ")"; if (!lvalue && a.element_type->is_simple()) *out << ")"; } void visit_eq(const Eq &n) final { if (lvalue) invalid(n); if (!n.lhs->type()->is_simple()) { assert(!n.rhs->type()->is_simple() && "comparison between simple and complex type"); *this << "handle_eq(" << *n.lhs << ", " << *n.rhs << ")"; } else { *this << "(" << *n.lhs << " == " << *n.rhs << ")"; } } void visit_exists(const Exists &n) final { if (lvalue) invalid(n); *out << "({ bool result = false; "; generate_quantifier_header(*out, n.quantifier); *this << "if (" << *n.expr << ") { result = true; break; }"; generate_quantifier_footer(*out, n.quantifier); *out << " result; })"; } void visit_exprid(const ExprID &n) final { if (n.value == nullptr) throw Error("symbol \"" + n.id + "\" in expression is unresolved", n.loc); if (lvalue && !n.is_lvalue()) invalid(n); /* This is a reference to a const. Note, this also covers enum * members. */ if (auto c = dynamic_cast(n.value.get())) { assert(!lvalue && "const appearing as an lvalue"); *out << "VALUE_C(" << c->value->constant_fold() << ")"; return; } // This is either a state variable, a local variable or an alias. if (isa(n.value) || isa(n.value)) { const Ptr t = n.type(); assert((!n.is_lvalue() || t != nullptr) && "lvalue without a type"); if (!lvalue && n.is_lvalue() && t->is_simple()) { const std::string lb = t->lower_bound(); const std::string ub = t->upper_bound(); *out << "handle_read(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << lb << ", " << ub << ", "; } *out << "ru_" << n.id; if (!lvalue && n.is_lvalue() && t->is_simple()) *out << ")"; return; } // FIXME: there's another case here where it's a reference to a quanitified // variable. I suspect we should just handle that the same way as a local. } void visit_field(const Field &n) final { if (lvalue && !n.is_lvalue()) invalid(n); const Ptr root = n.record->type(); assert(root != nullptr); const Ptr resolved = root->resolve(); assert(resolved != nullptr); if (auto r = dynamic_cast(resolved.get())) { mpz_class offset = 0; for (const Ptr &f : r->fields) { if (f->name == n.field) { if (!lvalue && f->type->is_simple()) { const std::string lb = f->type->lower_bound(); const std::string ub = f->type->upper_bound(); *out << "handle_read(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << lb << ", " << ub << ", "; } *out << "handle_narrow("; if (lvalue) { generate_lvalue(*out, *n.record); } else { generate_rvalue(*out, *n.record); } *out << ", " << offset << ", " << f->type->width() << ")"; if (!lvalue && f->type->is_simple()) *out << ")"; return; } offset += f->type->width(); } throw Error("no field named \"" + n.field + "\" in record", n.loc); } throw Error("left hand side of field expression is not a record", n.loc); } void visit_forall(const Forall &n) final { if (lvalue) invalid(n); *out << "({ bool result = true; "; generate_quantifier_header(*out, n.quantifier); *this << "if (!" << *n.expr << ") { result = false; break; }"; generate_quantifier_footer(*out, n.quantifier); *out << " result; })"; } void visit_functioncall(const FunctionCall &n) final { if (lvalue) invalid(n); if (n.function == nullptr) throw Error("unresolved function reference " + n.name, n.loc); const Ptr &return_type = n.function->return_type; // Open a statement-expression so we can declare temporaries. *out << "({ "; /* Secondly, a read-only value is never passed to a var parameter. This * should have been validated by FunctionCall::validate(). */ { auto it = n.function->parameters.begin(); for (const Ptr &a __attribute__((unused)) : n.arguments) { assert(((*it)->is_readonly() || !a->is_readonly()) && "read-only value passed to var parameter"); it++; } } /* Now for each parameter we need to consider four distinct methods, based * on the parameter's circumstance as described in the following table: * * ┌──────┬────────────────┬─────────┬────────────╥────────┐ * │ var? │ simple/complex │ lvalue? │ read-only? ║ method │ * ├──────┼────────────────┼─────────┼────────────╫────────┤ * │ no │ simple │ no │ - ║ 1 │ * │ no │ simple │ yes │ no ║ 2 │ * │ no │ simple │ yes │ yes ║ 2 │ * │ no │ complex │ no │ - ║ 5 │ * │ no │ complex │ yes │ no ║ 3 │ * │ no │ complex │ yes │ yes ║ 3 │ * │ yes │ simple │ no │ no ║ 1 │ * │ yes │ simple │ yes │ no ║ 4 │ * │ yes │ complex │ yes │ no ║ 4 │ * └──────┴────────────────┴─────────┴────────────╨────────┘ * * 1. We can create a temporary handle and backing storage, then extract * the value of the argument as an rvalue and write it to this * temporary. The temporary can then be passed into the function, * ensuring we don't modify the original argument. * * 2. We can do the same as (1), but extract the value of the argument * with handle_read_raw. We need to do this because the argument might * be undefined, in which case we want to extract its value without * error. Another wrinkle we need to handle here is that the argument * might be of a different range type than the function parameter type * (differing lower and upper bounds). * * 3. We can create a temporary handle and backing store and then use * handle_copy to transfer the value of the original argument. This is * correct as we know the argument and the parameter it will be passed * as have identical width. * * 4. We just pass the original handle, the lvalue of the argument. * * 5. We pass the original (rvalue) handle. */ auto get_method = [](const Ptr ¶meter, const Ptr &argument) { bool var = !parameter->is_readonly(); bool simple = parameter->type->is_simple(); bool is_lvalue = argument->is_lvalue(); bool readonly = argument->is_readonly(); if (!var && simple && !is_lvalue ) return 1; if (!var && simple && is_lvalue && !readonly) return 2; if (!var && simple && is_lvalue && readonly) return 2; if (!var && !simple && !is_lvalue ) return 5; if (!var && !simple && is_lvalue && !readonly) return 3; if (!var && !simple && is_lvalue && readonly) return 3; if ( var && simple && !is_lvalue ) return 1; if ( var && simple && is_lvalue && !readonly) return 4; if ( var && !simple && !readonly) return 4; assert(!"unreachable"); __builtin_unreachable(); }; // Create the temporaries for each argument. { size_t index = 0; auto it = n.function->parameters.begin(); for (const Ptr &a : n.arguments) { const Ptr &p = *it; const std::string storage = "v" + std::to_string(n.unique_id) + "_" + std::to_string(index) + "_"; const std::string handle = "v" + std::to_string(n.unique_id) + "_" + std::to_string(index); auto method = get_method(p, a); assert(method >= 1 && method <= 5); if (method == 1 || method == 2 || method == 3) *out << "uint8_t " << storage << "[BITS_TO_BYTES(" << p->width() << ")] = { 0 }; " << "struct handle " << handle << " = { .base = " << storage << ", .offset = 0, .width = " << p->width() << "ull }; "; if (method == 1) { const std::string lb = p->get_type()->lower_bound(); const std::string ub = p->get_type()->upper_bound(); *out << "handle_write(" << to_C_string(n.loc) << ", rule_name, " << "\"\", s, " << lb << ", " << ub << ", " << handle << ", "; generate_rvalue(*out, *a); *out << "); "; } else if (method == 2) { const std::string lb = p->get_type()->lower_bound(); const std::string ub = p->get_type()->upper_bound(); const std::string lba = a->type()->lower_bound(); *out << "{ " << "raw_value_t v = handle_read_raw(s, "; generate_lvalue(*out, *a); *out << "); " << "raw_value_t v2; " << "value_t v3; " << "static const value_t lb = " << lb << "; " << "static const value_t ub = " << ub << "; " << "if (v != 0 && (SUB(v, 1, &v2) || ADD(v2, " << lba << ", &v3) " << "|| v3 < lb || v3 > ub)) { " << "error(s, \"call to function %s passed an out-of-range value " << "%\" PRIRAWVAL \" to parameter " << (index + 1) << "\", \"" << n.name << "\", raw_value_to_string(v + " << lba << " - 1)); " << "} " << "handle_write_raw(s, " << handle << ", v == 0 ? v : " << "((raw_value_t)(v3 - " << lb << ") + 1)); " << "} "; } else if (method == 3) { assert(a->type()->width() == p->width() && "complex function " "parameter receiving an argument of a differing width"); *out << "handle_copy(" << handle << ", "; generate_lvalue(*out, *a); *out << "); "; } it++; index++; } } *out << "ru_" << n.name << "(rule_name, state_drop_const(s)"; // Pass the return type output parameter if required. if (return_type != nullptr && !return_type->is_simple()) *out << ", (struct handle){ .base = ret" << n.unique_id << ", .offset = 0ul, .width = " << return_type->width() << "ull }"; // Now emit the arguments to the function. { size_t index = 0; auto it = n.function->parameters.begin(); for (const Ptr &a : n.arguments) { *out << ", "; assert(it != n.function->parameters.end() && "function call has more arguments than its target function"); const Ptr &p = *it; const std::string handle = "v" + std::to_string(n.unique_id) + "_" + std::to_string(index); switch (get_method(p, a)) { case 4: generate_lvalue(*out, *a); break; case 5: generate_rvalue(*out, *a); break; default: *out << handle; break; } index++; it++; } } *out << ");"; // Close the statement-expression. *out << " })"; } void visit_geq(const Geq &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " >= " << *n.rhs << ")"; } void visit_gt(const Gt &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " > " << *n.rhs << ")"; } void visit_implication(const Implication &n) final { if (lvalue) invalid(n); *this << "(!" << *n.lhs << " || " << *n.rhs << ")"; } void visit_isundefined(const IsUndefined &n) final { *this << "handle_isundefined(s, "; generate_lvalue(*out, *n.expr); *this << ")"; } void visit_leq(const Leq &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " <= " << *n.rhs << ")"; } void visit_lt(const Lt &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " < " << *n.rhs << ")"; } void visit_mod(const Mod &n) final { if (lvalue) invalid(n); *this << "mod(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.lhs << ", " << *n.rhs << ")"; } void visit_mul(const Mul &n) final { if (lvalue) invalid(n); *this << "mul(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.lhs << ", " << *n.rhs << ")"; } void visit_negative(const Negative &n) final { if (lvalue) invalid(n); *this << "negate(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.rhs << ")"; } void visit_neq(const Neq &n) final { if (lvalue) invalid(n); if (!n.lhs->type()->is_simple()) { assert(!n.rhs->type()->is_simple() && "comparison between simple and complex type"); *this << "(!handle_eq(" << *n.lhs << ", " << *n.rhs << "))"; } else { *this << "(" << *n.lhs << " != " << *n.rhs << ")"; } } void visit_not(const Not &n) final { if (lvalue) invalid(n); *this << "(!" << *n.rhs << ")"; } void visit_number(const Number &n) final { *out << "VALUE_C(" << n.value << ")"; } void visit_or(const Or &n) final { if (lvalue) invalid(n); *this << "(" << *n.lhs << " || " << *n.rhs << ")"; } void visit_sub(const Sub &n) final { if (lvalue) invalid(n); *this << "sub(" << to_C_string(n.loc) << ", rule_name, " << to_C_string(n) << ", s, " << *n.lhs << ", " << *n.rhs << ")"; } void visit_ternary(const Ternary &n) final { if (lvalue) invalid(n); *this << "(" << *n.cond << " ? " << *n.lhs << " : " << *n.rhs << ")"; } virtual ~Generator() = default; private: void invalid(const Expr &n) const { throw Error("invalid expression used as lvalue", n.loc); } }; } void generate_lvalue(std::ostream &out, const Expr &e) { Generator g(out, true); g.dispatch(e); } void generate_rvalue(std::ostream &out, const Expr &e) { Generator g(out, false); g.dispatch(e); } rumur-2020.02.17/rumur/src/generate-function.cc000066400000000000000000000066551362265074000211650ustar00rootroot00000000000000#include #include "generate.h" #include #include #include #include "utils.h" #include using namespace rumur; void generate_function(std::ostream &out, const Function &f, const std::vector> &decls) { out << "static "; bool needs_return_handle = f.return_type != nullptr && !f.return_type->is_simple(); // if this function has no side effects and doesn’t have output arguments, // pass the compiler a hint for this if (!needs_return_handle && f.is_pure()) out << "__attribute__((pure)) "; /* Functions returning a simple type return a value, as expected. Functions * returning a complex type return a handle that is actually the same as their * second parameter (see below). */ if (f.return_type == nullptr) { /* We need to give void-returning functions a dummy boolean return type to * align with the type signature for rules. More specifically a return * can appear either within a rule or within a function/procedure. Within a * rule, it needs to return true to indicate to the caller no errors were * encountered during the rule. To allow a return statement to be emitted * uniformly without having to first check whether its within a rule or a * function/procedure we make the latter return a (ignored) boolean as well. */ out << "bool"; } else if (f.return_type->is_simple()) { out << "value_t"; } else { out << "struct handle"; } out << " ru_" << f.name << "(const char *rule_name __attribute__((unused)), " << "struct state *NONNULL s __attribute__((unused))"; // If required, generate the return (out) parameter. if (needs_return_handle) out << ", struct handle ret"; for (const Ptr &p : f.parameters) out << ", struct handle ru_" << p->name; out << ") {\n"; /* Output the state variable handles so we can reference them within * this start state. */ for (const Ptr &d : decls) { if (isa(d)) { /* Exciting kludge: we need to suppress the definition of state variables * that are shadowed by function parameters. Yes, real world models seem * to do this. */ bool shadowed = false; for (const Ptr &p : f.parameters) { if (p->name == d->name) { shadowed = true; break; } } if (shadowed) continue; out << " "; generate_decl(out, *d); out << ";\n"; } } /* Open a scope to support local declarations can shadow the state * variables. */ out << " {\n"; // Output this function's local decls for (const Ptr &d : f.decls) { if (isa(d) || isa(d)) { out << " "; generate_decl(out, *d); out << ";\n"; } } // Allocate memory for any complex-returning functions we call generate_allocations(out, f.body); // Generate the body of the function for (auto &s : f.body) { out << " "; generate_stmt(out, *s); out << ";\n"; } /* Emit a guard against the user leaving a control flow path through their * function that doesn't return. */ if (f.return_type != nullptr) out << " error(s, \"The end of function %s reached without returning " << "values.\", \"" << f.name << "\");\n"; if (f.return_type == nullptr) out << " return true; /* ignored by caller */\n"; // Close the scope we created. out << " }\n"; out << "}"; } rumur-2020.02.17/rumur/src/generate-model.cc000066400000000000000000001263441362265074000204360ustar00rootroot00000000000000#include #include "generate.h" #include #include #include #include #include #include "symmetry-reduction.h" #include "utils.h" #include using namespace rumur; static std::string rule_name_string(const Rule &r, size_t index) { if (r.name == "") return std::to_string(index + 1); return "\\\"" + escape(r.name) + "\\\""; } void generate_model(std::ostream &out, const Model &m) { // Generate each defined constant. for (const Ptr &d : m.decls) { if (isa(d)) { generate_decl(out, *d); out << ";\n"; } } // Generate each defined function or procedure. for (const Ptr &f : m.functions) { generate_function(out, *f, m.decls); out << "\n\n"; } /* Generate a set of flattened (non-hierarchical) rules. The purpose of this * is to essentially remove rulesets from the cases we need to deal with * below. */ std::vector> flat_rules; for (const Ptr &r : m.rules) { std::vector> rs = r->flatten(); flat_rules.insert(flat_rules.end(), rs.begin(), rs.end()); } // Write out the start state rules. { size_t index = 0; for (const Ptr &r : flat_rules) { if (auto s = dynamic_cast(r.get())) { out << "static bool startstate" << index << "(struct state *NONNULL s"; for (const Quantifier &q : s->quantifiers) out << ", struct handle ru_" << q.name; out << ") {\n"; out << " static const char *rule_name __attribute__((unused)) = \"startstate " << rule_name_string(*s, index) << "\";\n"; out << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* error triggered during this startstate */\n" << " return false;\n" << " }\n" << " }\n"; /* Output the state variable handles so we can reference them within * this start state. */ for (const Ptr &d : m.decls) { if (isa(d)) { out << " "; generate_decl(out, *d); out << ";\n"; } } /* Output alias definitions, opening a scope in advance to support * aliases that shadow state variables, parameters, or other aliases. */ for (const Ptr &a : s->aliases) { out << " {\n "; generate_decl(out, *a); out << ";\n"; } /* Open a scope to support local declarations can shadow the state * variables. */ out << " {\n"; for (const Ptr &d : s->decls) { if (auto v = dynamic_cast(d.get())) { out << " "; generate_decl(out, *v); out << ";\n"; } } // Allocate memory for any complex-returning functions we call generate_allocations(out, s->body); for (auto &st : s->body) { out << " "; generate_stmt(out, *st); out << ";\n"; } // Close the scopes we created. out << " }\n" << std::string(s->aliases.size(), '}') << "\n" << " return true;\n" << "}\n\n"; index++; } } } // Write out the property rules. { size_t index = 0; for (const Ptr &r : flat_rules) { if (auto i = dynamic_cast(r.get())) { out << "static __attribute__((unused)) bool property" << index << "(const struct state *NONNULL s"; for (const Quantifier &q : i->quantifiers) out << ", struct handle ru_" << q.name; out << ") {\n"; out << " static const char *rule_name __attribute__((unused)) = \"property " << rule_name_string(*i, index) << "\";\n"; /* Output the state variable handles so we can reference them within * this property. */ for (const Ptr &d : m.decls) { if (isa(d)) { out << " "; generate_decl(out, *d); out << ";\n"; } } /* Output alias definitions, opening a scope in advance to support * aliases that shadow state variables, parameters, or other aliases. */ for (const Ptr &a : i->aliases) { out << " {\n "; generate_decl(out, *a); out << ";\n"; } out << " return "; generate_property(out, i->property); out << ";\n" << std::string(i->aliases.size(), '}') << "\n" << "}\n\n"; index++; } } } // Write out the regular rules. { size_t index = 0; for (const Ptr &r : flat_rules) { if (auto s = dynamic_cast(r.get())) { // Write the guard out << "static int guard" << index << "(const struct state *NONNULL s " "__attribute__((unused))"; for (const Quantifier &q : s->quantifiers) out << ", struct handle ru_" << q.name << " __attribute__((unused))"; out << ") {\n"; out << " static const char *rule_name __attribute__((unused)) = \"" << "guard of rule " << rule_name_string(*s, index) << "\";\n"; out << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* this guard triggered an error */\n" << " return -1;\n" << " }\n" << " }\n"; /* Output the state variable handles so we can reference them within * this guard. */ for (const Ptr &d : m.decls) { if (isa(d)) { out << " "; generate_decl(out, *d); out << ";\n"; } } /* Output alias definitions, opening a scope in advance to support * aliases that shadow state variables, parameters, or other aliases. */ for (const Ptr &a : s->aliases) { out << " {\n "; generate_decl(out, *a); out << ";\n"; } out << " return "; if (s->guard == nullptr) { out << "true"; } else { generate_rvalue(out, *s->guard); } out << " ? 1 : 0;\n" << std::string(s->aliases.size(), '}') << "\n" << "}\n\n"; // Write the body out << "static bool rule" << index << "(struct state *NONNULL s"; for (const Quantifier &q : s->quantifiers) out << ", struct handle ru_" << q.name; out << ") {\n"; out << " static const char *rule_name __attribute__((unused)) = \"rule " << rule_name_string(*s, index) << "\";\n"; out << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* an error was triggered during this rule */\n" << " return false;\n" << " }\n" << " }\n"; /* Output the state variable handles so we can reference them within * this rule. */ for (const Ptr &d : m.decls) { if (isa(d)) { out << " "; generate_decl(out, *d); out << ";\n"; } } /* Output alias definitions, opening a scope in advance to support * aliases that shadow state variables, parameters, or other aliases. */ for (const Ptr &a : s->aliases) { out << " {\n "; generate_decl(out, *a); out << ";\n"; } /* Open a scope to support local declarations can shadow the state * variables. */ out << " {\n"; for (const Ptr &d : s->decls) { if (isa(d)) { out << " "; generate_decl(out, *d); out << ";\n"; } } // Allocate memory for any complex-returning functions we call generate_allocations(out, s->body); for (auto &st : s->body) { out << " "; generate_stmt(out, *st); out << ";\n"; } // Close the scopes we created. out << " }\n" << std::string(s->aliases.size(), '}') << "\n" << " return true;\n" << "}\n\n"; index++; } } } // Write invariant checker { out << "static bool check_invariants(const struct state *NONNULL s " << "__attribute__((unused))) {\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* invariant violated */\n" << " return false;\n" << " }\n" << " }\n"; size_t index = 0; size_t invariant_index = 0; for (const Ptr &r : flat_rules) { if (auto p = dynamic_cast(r.get())) { if (p->property.category == Property::ASSERTION) { // Open a scope so we don't have to think about name collisions. out << " {\n"; // Set up quantifiers. for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out << " if (!property" << index << "(s"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " error(s, \"invariant %s failed\", \"" << rule_name_string(*p, invariant_index) << "\");\n" << " }\n"; // Close the quantifier loops. for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // Close this invariant's scope. out << " }\n"; invariant_index++; } index++; } } out << " return true;\n" << "}\n\n"; } // Write assumption checker { out << "static bool check_assumptions(const struct state *NONNULL s " << "__attribute__((unused))) {\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* one of the properties triggered an error */\n" << " return false;\n" << " }\n" << " }\n"; size_t index = 0; for (const Ptr &r : flat_rules) { if (auto p = dynamic_cast(r.get())) { if (p->property.category == Property::ASSUMPTION) { // Open a scope so we don't have to think about name collisions. out << " {\n"; // Set up quantifiers. for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out << " if (!property" << index << "(s"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* Assumption violated. */\n" << " return false;\n" << " }\n"; // Close the quantifier loops. for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // Close this invariant's scope. out << " }\n"; } index++; } } out << " return true;\n" << "}\n\n"; } // Write cover checker { out << "static bool check_covers(const struct state *NONNULL s " << "__attribute__((unused))) {\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* one of the properties triggered an error */\n" << " return false;\n" << " }\n" << " }\n"; size_t index = 0; for (const Ptr &r : flat_rules) { if (auto p = dynamic_cast(r.get())) { if (p->property.category == Property::COVER) { // Open a scope so we don't have to think about name collisions. out << " {\n"; // Set up quantifiers. for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out << " if (property" << index << "(s"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* Covered. */\n" << " (void)__atomic_fetch_add(&covers[COVER_" << p->property.unique_id << "], 1, __ATOMIC_SEQ_CST);\n" << " }\n"; // Close the quantifier loops. for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // Close this cover's scope. out << " }\n"; } index++; } } out << " return true;\n" << "}\n\n"; } // Write liveness checker { out << "#if LIVENESS_COUNT > 0\n" << "static bool check_liveness(struct state *NONNULL s " << "__attribute__((unused))) {\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << " if (JMP_BUF_NEEDED) {\n" << " if (sigsetjmp(checkpoint, 0)) {\n" << " /* one of the liveness properties triggered an error */\n" << " return false;\n" << " }\n" << " }\n" << " size_t liveness_index __attribute__((unused)) = 0;\n"; size_t index = 0; for (const Ptr &r : flat_rules) { if (auto p = dynamic_cast(r.get())) { if (p->property.category == Property::LIVENESS) { // Open a scope so we don't have to think about name collisions. out << " {\n"; // Set up quantifiers. for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out << " if (property" << index << "(s"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* Hit. */\n" << " mark_liveness(s, liveness_index, false);\n" << " }\n" << " liveness_index++;\n"; // Close the quantifier loops. for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // Close this cover's scope. out << " }\n"; } index++; } } out << " return true;\n" << "}\n\n"; } // Write final liveness checker, the one that runs just prior to termination { out << "static void check_liveness_final(void) {\n" << "\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << "\n" << " if (!MACHINE_READABLE_OUTPUT) {\n" << " printf(\"trying to prove remaining liveness constraints...\\n\");\n" << " }\n" << "\n" << " /* find how many liveness bits are unknown */\n" << " unsigned long remaining = 0;\n" << " unsigned long long last_update = 0;\n" << " unsigned long learned_since_last = 0;\n" << " if (!MACHINE_READABLE_OUTPUT) {\n" << " for (size_t i = 0; i < set_size(local_seen); i++) {\n" << "\n" << " slot_t slot = __atomic_load_n(&local_seen->bucket[i], __ATOMIC_SEQ_CST);\n" << "\n" << " ASSERT(!slot_is_tombstone(slot)\n" << " && \"seen set being migrated during final liveness check\");\n" << "\n" << " if (slot_is_empty(slot)) {\n" << " /* skip empty entries in the hash table */\n" << " continue;\n" << " }\n" << "\n" << " struct state *s = slot_to_state(slot);\n" << " ASSERT(s != NULL && \"null pointer stored in state set\");\n" << "\n" << " remaining += unknown_liveness(s);\n" << " }\n" << " printf(\"\\t %lu constraints remaining\\n\", remaining);\n" << " last_update = gettime();\n" << " }\n" << "\n" << " bool progress = true;\n" << " while (progress) {\n" << " progress = false;\n" << "\n" << " /* Run through all seen states trying to learn new liveness information. */\n" << " for (size_t i = 0; i < set_size(local_seen); i++) {\n" << "\n" << " slot_t slot = __atomic_load_n(&local_seen->bucket[i], __ATOMIC_SEQ_CST);\n" << "\n" << " ASSERT(!slot_is_tombstone(slot)\n" << " && \"seen set being migrated during final liveness check\");\n" << "\n" << " if (slot_is_empty(slot)) {\n" << " /* skip empty entries in the hash table */\n" << " continue;\n" << " }\n" << "\n" << " struct state *s = slot_to_state(slot);\n" << " ASSERT(s != NULL && \"null pointer stored in state set\");\n" << "\n" << " if (unknown_liveness(s) == 0) {\n" << " /* skip entries where liveness is fully satisfied already */\n" << " continue;\n" << " }\n" << "\n" << "#if BOUND > 0\n" << " /* If we're doing bounded checking and this state is at the bound limit,\n" << " * it's not valid to expand beyond this.\n" << " */\n" << " ASSERT(state_bound_get(s) <= BOUND && \"a state that exceeded the bound depth was explored\");\n" << " if (state_bound_get(s) == BOUND) {\n" << " continue;\n" << " }\n" << "#endif\n" << "\n"; size_t index = 0; for (const Ptr &r : flat_rules) { if (isa(r)) { // Open a scope so we don't have to think about name collisions. out << " {\n"; for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out // Use a dummy do-while to give us 'break' as a local goto. << " do {\n" << " struct state *n = state_dup(s);\n" << "\n" << " int g = guard" << index << "(n"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ");\n" << " if (g == -1) {\n" << " /* guard triggered an error */\n" << " state_free(n);\n" << " break;\n" << " } else if (g == 1) {\n" << " if (!rule" << index << "(n"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* this rule triggered an error */\n" << " state_free(n);\n" << " break;\n" << " }\n" << " state_canonicalise(n);\n" << " if (!check_assumptions(n)) {\n" << " /* assumption violated */\n" << " state_free(n);\n" << " break;\n" << " }\n" << "\n" << " /* note that we can skip an invariant check because we already know it\n" << " * passed from prior expansion of this state.\n" << " */\n" << "\n" << " /* We should be able to find this state in the seen set. */\n" << " const struct state *t = set_find(n);\n" << " ASSERT(t != NULL && \"state encountered during final liveness wrap up \"\n" << " \"that was not previously seen\");\n" << "\n" << " /* See if this successor state learned a liveness property it never\n" << " * passed back to us. This can occur if the state our exploration\n" << " * encountered (`n`) was not the first of its kind seen and thus was\n" << " * de-duped and never made it into the seen set with a back pointer\n" << " * to `s`.\n" << " */\n" << " unsigned long learned = learn_liveness(s, t);\n" << " if (learned > 0) {\n" << " if (!MACHINE_READABLE_OUTPUT) {\n" << " learned_since_last += learned;\n" << " remaining -= learned;\n" << " unsigned long long t = gettime();\n" << " if (t > last_update) {\n" << " printf(\"\\t %lu further liveness constraints proved in %llus, with %s%lu%s remaining\\n\",\n" << " learned_since_last, t - last_update, green(), remaining, reset());\n" << " learned_since_last = 0;\n" << " last_update = t;\n" << " }\n" << " }\n" << " progress = true;\n" << " }\n" << " }\n" << " /* we don't need this state anymore. */\n" << " state_free(n);\n" << " } while (0);\n"; // Close the quantifier loops. for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // Close this rule's scope. out << "}\n"; index++; } } out << " }\n" << " }\n" << "}\n" << "\n" << "\n" << "static unsigned long check_liveness_summarise(void) {\n" << "\n" << " /* We can now finally check whether all liveness properties were hit. */\n" << " bool missed[LIVENESS_COUNT];\n" << " memset(missed, 0, sizeof(missed));\n" << " for (size_t i = 0; i < set_size(local_seen); i++) {\n" << "\n" << " slot_t slot = __atomic_load_n(&local_seen->bucket[i], __ATOMIC_SEQ_CST);\n" << "\n" << " ASSERT(!slot_is_tombstone(slot)\n" << " && \"seen set being migrated during final liveness check\");\n" << "\n" << " if (slot_is_empty(slot)) {\n" << " /* skip empty entries in the hash table */\n" << " continue;\n" << " }\n" << "\n" << " const struct state *s = slot_to_state(slot);\n" << " ASSERT(s != NULL && \"null pointer stored in state set\");\n" << "\n" << " size_t index __attribute__((unused)) = 0;\n"; index = 0; for (const Ptr &r : flat_rules) { if (auto p = dynamic_cast(r.get())) { if (p->property.category == Property::LIVENESS) { // Open a scope so we don't have to think about name collisions. out << " {\n"; // Set up quantifiers. Note, in this case they are not used. for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out << " size_t word_index = index / (sizeof(s->liveness[0]) * CHAR_BIT);\n" << " size_t bit_index = index % (sizeof(s->liveness[0]) * CHAR_BIT);\n" << " if (!missed[index] && !((s->liveness[word_index] >> bit_index) & 0x1)) {\n" << " /* missed */\n" << " missed[index] = true;\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\\nliveness property \", " << "COUNTEREXAMPLE_TRACE == CEX_OFF ? \"false\" : \"true\");\n" << " xml_printf(\"" << rule_name_string(*p, index) << "\");\n" << " printf(\" violated\\n\");\n" << " } else {\n" << " printf(\"\\t%s%sliveness property %s violated:%s\\n\", red(), bold(), \"" << rule_name_string(*p, index) << "\", reset());\n" << " }\n" << " print_counterexample(s);\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\\n\");\n" << " }\n" << " }\n" << " index++;\n"; // Close the quantifier loops. for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // Close this cover's scope. out << " }\n"; index++; } } } out << " }\n" << "\n" << " /* total up how many misses we saw */\n" << " unsigned long total = 0;\n" << " for (size_t i = 0; i < sizeof(missed) / sizeof(missed[0]); i++) {\n" << " if (missed[i]) {\n" << " total++;\n" << " }\n" << " }\n" << "\n" << " return total;\n" << "}\n" << "#endif\n" << "\n"; } // Write out the symmetry reduction canonicalisation function generate_canonicalise(m, out); out << "\n\n"; // Write initialisation { out << "static void init(void) {\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << " size_t queue_id = 0;\n" << " uint64_t rule_taken = 1;\n"; size_t index = 0; for (const Ptr &r : flat_rules) { if (isa(r)) { // Open a scope so we don't have to think about name collisions. out << " {\n"; /* Define the state variable because the code emitted for quantifiers * expects it. They do not need a non-NULL value. */ out << " struct state *s = NULL;\n"; // Set up quantifiers. for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out // Use a dummy do-while to give us 'break' as a local goto. << " do {\n" << " s = state_new();\n" << " memset(s, 0, sizeof(*s));\n" << "#if COUNTEREXAMPLE_TRACE != CEX_OFF\n" << " state_rule_taken_set(s, rule_taken);\n" << "#endif\n" << " if (!startstate" << index << "(s"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* startstate triggered an error */\n" << " state_free(s);\n" << " break;\n" << " }\n" << " state_canonicalise(s);\n" << " if (!check_assumptions(s)) {\n" << " /* assumption violated */\n" << " state_free(s);\n" << " break;\n" << " }\n" << " if (!check_invariants(s)) {\n" << " /* invariant violated */\n" << " state_free(s);\n" << " break;\n" << " }\n" << " size_t size;\n" << " if (set_insert(s, &size)) {\n" << " if (!check_covers(s)) {\n" << " /* one of the cover properties triggered an error */\n" << " break;\n" << " }\n" << "#if LIVENESS_COUNT > 0\n" << " if (!check_liveness(s)) {\n" << " /* one of the liveness properties triggered an error */\n" << " break;\n" << " }\n" << "#endif\n" << " (void)queue_enqueue(s, queue_id);\n" << " queue_id = (queue_id + 1) % (sizeof(q) / sizeof(q[0]));\n" << " } else {\n" << " state_free(s);\n" << " }\n" << " } while (0);\n" << " rule_taken++;\n"; // Close the quantifier loops. for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // Close this startstate's scope. out << " }\n"; index++; } } out << "}\n\n"; } // Write exploration logic { out << "static void explore(void) {\n" << "\n" << " /* Used when writing to quantifier variables. */\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << "\n" << " size_t last_queue_size = 0;\n" << "\n" << " /* Identifier of the last queue we interacted with. */\n" << " size_t queue_id = thread_id;\n" << "\n" << " for (;;) {\n" << "\n" << " if (THREADS > 1 && __atomic_load_n(&error_count,\n" << " __ATOMIC_SEQ_CST) >= MAX_ERRORS) {\n" << " /* Another thread found an error. */\n" << " break;\n" << " }\n" << "\n" << " const struct state *s = queue_dequeue(&queue_id);\n" << " if (s == NULL) {\n" << " break;\n" << " }\n" << "\n" << " bool possible_deadlock = true;\n" << " uint64_t rule_taken = 1;\n"; size_t index = 0; for (const Ptr &r : flat_rules) { if (isa(r)) { // Open a scope so we don't have to think about name collisions. out << " {\n"; for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out // Use a dummy do-while to give us 'break' as a local goto. << " do {\n" << " struct state *n = state_dup(s);\n" << "#if COUNTEREXAMPLE_TRACE != CEX_OFF\n" << " state_rule_taken_set(n, rule_taken);\n" << "#endif\n" << " int g = guard" << index << "(n"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ");\n" << " if (g == -1) {\n" << " /* error() was called */\n" << " state_free(n);\n" << " break;\n" << " } else if (g == 1) {\n" << " if (!rule" << index << "(n"; for (const Quantifier &q : r->quantifiers) out << ", ru_" << q.name; out << ")) {\n" << " /* this rule triggered an error */\n" << " state_free(n);\n" << " break;\n" << " }\n" << " rules_fired_local++;\n" << " if (DEADLOCK_DETECTION != DEADLOCK_DETECTION_STUTTERING || !state_eq(s, n)) {\n" << " possible_deadlock = false;\n" << " }\n" << " state_canonicalise(n);\n" << " if (!check_assumptions(n)) {\n" << " /* assumption violated */\n" << " state_free(n);\n" << " break;\n" << " }\n" << " if (!check_invariants(n)) {\n" << " /* invariant violated */\n" << " state_free(n);\n" << " break;\n" << " }\n" << " size_t size;\n" << " if (set_insert(n, &size)) {\n" << "\n" << " if (!check_covers(n)) {\n" << " /* one of the cover properties triggered an error */\n" << " break;\n" << " }\n" << "#if LIVENESS_COUNT > 0\n" << " if (!check_liveness(n)) {\n" << " /* one of the liveness properties triggered an error */\n" << " break;\n" << " }\n" << "#endif\n" << "\n" << "#if BOUND > 0\n" << " if (state_bound_get(n) < BOUND) {\n" << "#endif\n" << " size_t queue_size = queue_enqueue(n, thread_id);\n" << " queue_id = thread_id;\n" << "\n" << " if (size % 10000 == 0) {\n" << " print_lock();\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\\n\", size, " << "gettime(), rules_fired_local, queue_size, thread_id);\n" << " } else {\n" << " printf(\"\\t \");\n" << " if (THREADS > 1) {\n" << " printf(\"thread %zu: \", thread_id);\n" << " }\n" << " printf(\"%zu states explored in %llus, with %\" PRIuMAX \" rules \"\n" << " \"fired and %s%zu%s states in the queue.\\n\", size, gettime(),\n" << " rules_fired_local, queue_size > last_queue_size ? yellow() : green(),\n" << " queue_size, reset());\n" << " }\n" << " print_unlock();\n" << " last_queue_size = queue_size;\n" << " }\n" << "\n" << " if (THREADS > 1 && thread_id == 0 && phase == WARMUP && queue_size > 20) {\n" << " start_secondary_threads();\n" << " phase = RUN;\n" << " }\n" << "\n" << "#if BOUND > 0\n" << " }\n" << "#endif\n" << " } else {\n" << " state_free(n);\n" << " }\n" << " } else {\n" << " state_free(n);\n" << " }\n" << " } while (0);\n" << " rule_taken++;\n"; // Close the quantifier loops. for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); // Close this rule's scope. out << "}\n"; index++; } } out << " /* If we did not toggle 'possible_deadlock' off by this point, we\n" << " * have a deadlock.\n" << " */\n" << " if (DEADLOCK_DETECTION != DEADLOCK_DETECTION_OFF && possible_deadlock) {\n" << " deadlock(s);\n" << " }\n" << "\n" << " }\n" << " exit_with(EXIT_SUCCESS);\n" << "}\n\n"; } // Write a function to print the state. out << "static void state_print(const struct state *previous, const struct " << "state *NONNULL s) {\n"; /* Output the state variable handles so we can reference them within this * function. */ for (const Ptr &d : m.decls) { if (isa(d)) { out << " "; generate_decl(out, *d); out << ";\n"; } } for (const Ptr &d : m.decls) { if (auto v = dynamic_cast(d.get())) generate_print(out, *v->type, v->name, "ru_" + v->name, true, true); } out << "}\n\n"; // Write a function to print state transitions. out << "static void print_transition(const struct state *NONNULL s " << "__attribute__((unused))) {\n" << " ASSERT(s != NULL);\n" << " static const char *rule_name __attribute__((unused)) = NULL;\n" << "#if COUNTEREXAMPLE_TRACE != CEX_OFF\n" << "\n" << " ASSERT(state_rule_taken_get(s) != 0 && \"unknown state transition\");\n" << "\n"; { out << " if (state_previous_get(s) == NULL) {\n" << " uint64_t rule_taken = 1;\n"; mpz_class base = 1; size_t index = 0; for (const Ptr &r : flat_rules) { if (isa(r)) { // Set up quantifiers. out << " {\n"; for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out << " if (state_rule_taken_get(s) == rule_taken) {\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\");\n" << " xml_printf(\"Startstate " << rule_name_string(*r, index) << "\");\n" << " } else {\n" << " printf(\"Startstate %s\", \"" << rule_name_string(*r, index) << "\");\n" << " }\n"; { size_t i = 0; for (const Quantifier &q : r->quantifiers) { out << " {\n" << " value_t v = (value_t)((rule_taken - " << base << ") / (1"; size_t j = r->quantifiers.size() - 1; for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) { if (i == j) break; out << " * " << it->count(); j--; } out << ") % " << q.count() << ") + " << q.lower_bound() << ";\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\");\n" << " } else {\n" << " printf(\", %s: \", \"" << q.name << "\");\n" << " }\n"; const Ptr t = q.type->resolve(); if (auto e = dynamic_cast(t.get())) { size_t member_index = 0; for (const std::pair &member : e->members) { out << " "; if (member_index > 0) out << "else "; out << "if (v == VALUE_C(" << member_index << ")) {\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " xml_printf(\"" << member.first << "\");\n" << " } else {\n" << " printf(\"%s\", \"" << member.first << "\");\n" << " }\n" << " }\n"; member_index++; } out << " else {\n" << " ASSERT(!\"illegal value for " << q.name << "\");\n" << " }\n"; } else { out << " printf(\"%\" PRIVAL, value_to_string(v));\n"; } out << " if (MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\");\n" << " }\n" << " }\n"; i++; } } out << " if (MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\\n\");\n" << " } else {\n" << " printf(\" fired.\\n\");\n" << " }\n" << " return;\n" << " }\n"; out << " rule_taken++;\n"; // Close the quantifier loops. for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); out << " }\n"; // update base for future comparison against rule_taken mpz_class inc = 1; for (const Quantifier &q : r->quantifiers) { inc *= q.count(); } base += inc; index++; } } } { out << " } else {\n" << " uint64_t rule_taken = 1;\n"; mpz_class base = 1; size_t index = 0; for (const Ptr &r : flat_rules) { if (isa(r)) { // Set up quantifiers. out << " {\n"; for (const Quantifier &q : r->quantifiers) generate_quantifier_header(out, q); out << " if (state_rule_taken_get(s) == rule_taken) {\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\");\n" << " xml_printf(\"Rule " << rule_name_string(*r, index) << "\");\n" << " } else {\n" << " printf(\"Rule %s\", \"" << rule_name_string(*r, index) << "\");\n" << " }\n"; { size_t i = 0; for (const Quantifier &q : r->quantifiers) { out << " {\n" << " value_t v = (value_t)((rule_taken - " << base << ") / (1"; size_t j = r->quantifiers.size() - 1; for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) { if (i == j) break; out << " * " << it->count(); j--; } out << ") % " << q.count() << ") + " << q.lower_bound() << ";\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\");\n" << " } else {\n" << " printf(\", %s: \", \"" << q.name << "\");\n" << " }\n"; const Ptr t = q.type->resolve(); if (auto e = dynamic_cast(t.get())) { size_t member_index = 0; for (const std::pair &member : e->members) { out << " "; if (member_index > 0) out << "else "; out << "if (v == VALUE_C(" << member_index << ")) {\n" << " if (MACHINE_READABLE_OUTPUT) {\n" << " xml_printf(\"" << member.first << "\");\n" << " } else {\n" << " printf(\"%s\", \"" << member.first << "\");\n" << " }\n" << " }\n"; member_index++; } out << " else {\n" << " ASSERT(!\"illegal value for " << q.name << "\");\n" << " }\n"; } else { out << " printf(\"%\" PRIVAL, value_to_string(v));\n"; } out << " if (MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\");\n" << " }\n" << " }\n"; i++; } } out << " if (MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\\n\");\n" << " } else {\n" << " printf(\" fired.\\n\");\n" << " }\n" << " return;\n" << " }\n"; out << " rule_taken++;\n"; // Close the quantifier loops. for (auto it = r->quantifiers.rbegin(); it != r->quantifiers.rend(); it++) generate_quantifier_footer(out, *it); out << " }\n"; // update base for future comparison against rule_taken mpz_class inc = 1; for (const Quantifier &q : r->quantifiers) { inc *= q.count(); } base += inc; index++; } } } out << " }\n" << "\n" << " /* give some helpful output for debugging problems with this function. */\n" << " fprintf(stderr, \"no rule found to link to state at depth %zu\\n\", state_depth(s));\n" << " ASSERT(!\"unreachable\");\n" << "#endif\n" << "}\n\n"; // Generate a function used during debugging out << "static void state_print_field_offsets(void) {\n" << " printf(\"\t* state struct is %zu-byte aligned\\n\", " "__alignof__(struct state));\n"; for (const Ptr &d : m.decls) { if (auto v = dynamic_cast(d.get())) out << " printf(\"\t* field %s is located at state offset " << v->offset << " bits\\n\", \"" << v->name << "\");\n"; } out << " printf(\"\\n\");\n" << "}\n\n"; } rumur-2020.02.17/rumur/src/generate-print.cc000066400000000000000000000173641362265074000204730ustar00rootroot00000000000000#include #include #include "generate.h" #include #include #include #include #include using namespace rumur; namespace { class Generator : public ConstTypeTraversal { private: std::ostream *out; const std::string prefix; const std::string handle; const bool support_diff; const bool support_xml; public: Generator(std::ostream &o, const std::string &p, const std::string &h, bool s, bool x): out(&o), prefix(p), handle(h), support_diff(s), support_xml(x) { } Generator(const Generator &caller, const std::string &p, const std::string &h): out(caller.out), prefix(p), handle(h), support_diff(caller.support_diff), support_xml(caller.support_xml) { } void visit_array(const Array &n) final { const Ptr t = n.index_type->resolve(); if (auto r = dynamic_cast(t.get())) { mpz_class lb = r->min->constant_fold(); mpz_class ub = r->max->constant_fold(); // FIXME: Unrolling this loop at generation time is not great if the index // type is big. E.g. if someone has an 'array [0..10000] of ...' this is // going to generate quite unpleasant code. mpz_class preceding_offset = 0; mpz_class w = n.element_type->width(); for (mpz_class i = lb; i <= ub; i++) { const std::string h = derive_handle(preceding_offset, w); Generator g(*this, prefix + "[" + i.get_str() + "]", h); g.dispatch(*n.element_type); preceding_offset += w; } return; } if (auto s = dynamic_cast(t.get())) { mpz_class b = s->bound->constant_fold(); mpz_class preceding_offset = 0; mpz_class w = n.element_type->width(); for (mpz_class i = 0; i < b; i++) { const std::string h = derive_handle(preceding_offset, w); Generator g(*this, prefix + "[" + i.get_str() + "]", h); g.dispatch(*n.element_type); preceding_offset += w; } return; } if (auto e = dynamic_cast(t.get())) { mpz_class preceding_offset = 0; mpz_class w = n.element_type->width(); for (const std::pair &m : e->members) { const std::string h = derive_handle(preceding_offset, w); Generator g(*this, prefix + "[" + m.first + "]", h); g.dispatch(*n.element_type); preceding_offset += w; } return; } assert(!"non-range, non-enum used as array index"); } void visit_enum(const Enum &n) final { const std::string previous_handle = to_previous(); *out << "{\n" << " raw_value_t v = handle_read_raw(s, " << handle << ");\n" << " raw_value_t v_previous = 0;\n"; if (!support_diff) *out << " const struct state *previous = NULL;\n"; *out << " if (previous != NULL) {\n" << " v_previous = handle_read_raw(previous, " << previous_handle << ");\n" << " }\n" << " if (previous == NULL || v != v_previous) {\n" << " if (" << support_xml << " && MACHINE_READABLE_OUTPUT) {\n" << " printf(\" &m : n.members) { *out << " } else if (v == VALUE_C(" << (i + 1) << ")) {\n" << " printf(\"%s\", \"" << m.first << "\");\n"; i++; } *out << " } else {\n" << " assert(!\"illegal value for " << prefix << "\");\n" << " }\n" << " if (" << support_xml << " && MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\\\"/>\");\n" << " }\n" << " printf(\"\\n\");\n" << " }\n" << "}\n"; } void visit_range(const Range &n) final { const std::string lb = n.lower_bound(); const std::string ub = n.upper_bound(); const std::string previous_handle = to_previous(); *out << "{\n" << " raw_value_t v = handle_read_raw(s, " << handle << ");\n" << " raw_value_t v_previous = 0;\n"; if (!support_diff) *out << " const struct state *previous = NULL;\n"; *out << " if (previous != NULL) {\n" << " v_previous = handle_read_raw(previous, " << previous_handle << ");\n" << " }\n" << " if (previous == NULL || v != v_previous) {\n" << " if (" << support_xml << " && MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\");\n" << " }\n" << " printf(\"\\n\");\n" << " }\n" << "}\n"; } void visit_record(const Record &n) final { mpz_class preceding_offset = 0; for (auto &f : n.fields) { mpz_class w = f->width(); const std::string h = derive_handle(preceding_offset, w); generate_print(*out, *f->type, prefix + "." + f->name, h, support_diff, support_xml); preceding_offset += w; } } void visit_scalarset(const Scalarset&) final { const std::string previous_handle = to_previous(); *out << "{\n" << " raw_value_t v = handle_read_raw(s, " << handle << ");\n" << " raw_value_t v_previous = 0;\n"; if (!support_diff) *out << " const struct state *previous = NULL;\n"; *out << " if (previous != NULL) {\n" << " v_previous = handle_read_raw(previous, " << previous_handle << ");\n" << " }\n" << " if (previous == NULL || v != v_previous) {\n" << " if (" << support_xml << " && MACHINE_READABLE_OUTPUT) {\n" << " printf(\"\");\n" << " }\n" << " printf(\"\\n\");\n" << " }\n" << "}\n"; } void visit_typeexprid(const TypeExprID &n) final { if (n.referent == nullptr) throw Error("unresolved type symbol \"" + n.name + "\"", n.loc); dispatch(*n.referent->value); } virtual ~Generator() = default; private: std::string derive_handle(mpz_class offset, mpz_class width) const { return "((struct handle){ .base = " + handle + ".base + (" + handle + ".offset + ((size_t)" + offset.get_str() + ")) / CHAR_BIT, .offset = (" + handle + ".offset + ((size_t)" + offset.get_str() + ")) % CHAR_BIT, " + ".width = " + width.get_str() + "ull })"; } std::string to_previous() const { return "((struct handle){ .base = (uint8_t*)previous->data + (" + handle + ".base - (const uint8_t*)s->data), .offset = " + handle + ".offset, .width = " + handle + ".width })"; } }; } void generate_print(std::ostream &out, const TypeExpr &e, const std::string &prefix, const std::string &handle, bool support_diff, bool support_xml) { Generator g(out, prefix, handle, support_diff, support_xml); g.dispatch(e); } rumur-2020.02.17/rumur/src/generate-property.cc000066400000000000000000000003211362265074000212040ustar00rootroot00000000000000#include #include "generate.h" #include #include using namespace rumur; void generate_property(std::ostream &out, const Property &p) { generate_rvalue(out, *p.expr); } rumur-2020.02.17/rumur/src/generate-quantifier.cc000066400000000000000000000161731362265074000215030ustar00rootroot00000000000000#include #include #include "generate.h" #include #include #include #include "utils.h" using namespace rumur; void generate_quantifier_header(std::ostream &out, const Quantifier &q) { /* Set up quantifiers. It might be surprising to notice that there is an extra * level of indirection here. A variable 'x' results in loop counter '_ru1_x', * storage array '_ru2_x' and handle 'ru_x'. We use three variables rather * than two in order to avoid rules that modify the ruleset parameters * (uncommon) affecting the loop counter. */ const std::string counter = "_ru1_" + q.name; const std::string block = "_ru2_" + q.name; const std::string handle = "ru_" + q.name; // Calculate the width of the loop counter type. Use the VarDecl that // references to this variable will be referring to. std::string width = "((size_t)" + q.decl->type->width().get_str() + "ull)"; // open a scope to allow us to use the names 'lb', 'ub', and 'step' without // worrying about collisions out << "{\n"; // Write out the upper bound, lower bound, and step in advance. We generate // these here, rather than inline so as to avoid evaluating them twice (once // here and then once in the generate_quantifier_footer) as they may contain // side effects. out << " const value_t lb = "; if (q.type == nullptr) { assert(q.from != nullptr); generate_rvalue(out, *q.from); } else { out << q.type->lower_bound(); } out << ";\n"; out << " const value_t ub = "; if (q.type == nullptr) { assert(q.to != nullptr); generate_rvalue(out, *q.to); } else { out << q.type->upper_bound(); } out << ";\n"; out << " const raw_value_t step = (raw_value_t)"; if (q.step == nullptr) { out << "(ub >= lb ? 1 : -1)"; } else { generate_rvalue(out, *q.step); } out << ";\n"; // if the step was not a generation-time constant, it is possible that it // could work out to be 0 at runtime resulting in an infinite loop out << " if (step == 0) {\n" << " error(s, \"infinite loop due to step being 0\");\n" << " }\n"; // it is also possible we find the iteration goes the wrong way out << "#ifdef __clang__\n" << " #pragma clang diagnostic push\n" << " #pragma clang diagnostic ignored \"-Wtautological-unsigned-zero-compare\"\n" << "#elif defined(__GNUC__)\n" << " #pragma GCC diagnostic push\n" << " #pragma GCC diagnostic ignored \"-Wtype-limits\"\n" << "#endif\n" << " if ((ub > lb && (value_t)step < 0) ||\n" << " (ub < lb && (value_t)step > 0)) {\n" << "#ifdef __clang__\n" << " #pragma clang diagnostic pop\n" << "#elif defined(__GNUC__)\n" << " #pragma GCC diagnostic pop\n" << "#endif\n" << " error(s, \"infinite loop due to step being in the wrong direction\");\n" << " }\n"; // Type lower bound, as distinct from the iteration lower bound. // bounds. References to this quantified variable will use these when // unpacking its compressed representation, so we need to offset the values we // store from it. const std::string lower = q.decl->type->lower_bound(); const std::string upper = q.decl->type->upper_bound(); out << "#if !defined(__clang__) && defined(__GNUC__)\n" << " #pragma GCC diagnostic push\n" << " #pragma GCC diagnostic ignored \"-Wtype-limits\"\n" << "#endif\n" << " ASSERT(lb >= " << lower << " && lb <= " << upper << " && " "\"iteration lower bound exceeds type limits\");\n" << " ASSERT(ub >= " << lower << " && ub <= " << upper << " && " "\"iteration upper bound exceeds type limits\");\n" << "#if !defined(__clang__) && defined(__GNUC__)\n" << " #pragma GCC diagnostic pop\n" << "#endif\n"; // shorthands for converting between value_t and raw_value_t when we know the // value we have is in range #define V_TO_RV(x) ("(((raw_value_t)" + std::string(x) + \ ") + (raw_value_t)1 - (raw_value_t)(" + q.decl->type->lower_bound() + "))") #define RV_TO_V(x) ("((value_t)(" + std::string(x) + \ " - (raw_value_t)1 + (raw_value_t)(" + q.decl->type->lower_bound() + ")))") // construct the pieces of our for-loop header const std::string init = "raw_value_t " + counter + " = " + V_TO_RV("lb"); const std::string cond = counter + " == " + V_TO_RV("lb") + " || " "(lb < ub && " + RV_TO_V(counter) + " <= ub) || " "(lb > ub && " + RV_TO_V(counter) + " >= ub)"; const std::string inc = counter + " += step"; out << " for (" << init << "; " << cond << "; " << inc << ") {\n" << " uint8_t " << block << "[BITS_TO_BYTES(" << width << ")] = { 0 };\n" << " struct handle " << handle << " = { .base = " << block << ", .offset = 0, .width = " << width << " };\n" << " handle_write_raw(s, " << handle << ", " << counter << ");\n"; } void generate_quantifier_footer(std::ostream &out, const Quantifier &q) { const std::string counter = "_ru1_" + q.name; // is this a loop whose last iteration will result in a numeric overflow? std::string will_overflow = RV_TO_V("max_ - step") + " < ub"; // are we on the last iteration? std::string last_iteration = counter + " > RAW_VALUE_MAX - step"; // does this loop count up? const std::string up_count = "ub >= lb && (value_t)step > 0"; out << " /* If this iteration runs right up to the type limits, the last\n" << " * increment will overflow and fail to terminate, so we guard\n" << " * against that here.\n" << " */\n" << " {\n" << " /* GCC issues spurious -Wsign-compare warnings when doing\n" << " * subtraction on raw_value_t literals, so we suppress these\n" << " * by indirecting via this local constant. For more information:\n" << " * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=38341\n" << " */\n" << " const raw_value_t max_ = RAW_VALUE_MAX;\n" << " if (" << up_count << " && " << will_overflow << " && " << last_iteration << ") {\n" << " break;\n" << " }\n" << " }\n"; // do the same for if it is a down-counting loop will_overflow = RV_TO_V("min_ - step") + " > lb"; last_iteration = counter + " > RAW_VALUE_MIN - step"; const std::string down_count = "ub <= lb && (value_t)step < 0"; out << "#ifdef __clang__\n" << " #pragma clang diagnostic push\n" << " #pragma clang diagnostic ignored \"-Wtautological-unsigned-zero-compare\"\n" << "#elif defined(__GNUC__)\n" << " #pragma GCC diagnostic push\n" << " #pragma GCC diagnostic ignored \"-Wtype-limits\"\n" << "#endif\n" << " {\n" << " /* see above explanation of why we use an extra constant here */\n" << " const raw_value_t min_ = RAW_VALUE_MIN;\n" << " if (" << down_count << " && " << will_overflow << " && " << last_iteration << ") {\n" << "#ifdef __clang__\n" << " #pragma clang diagnostic pop\n" << "#elif defined(__GNUC__)\n" << " #pragma GCC diagnostic pop\n" << "#endif\n" << " break;\n" << " }\n" << " }\n"; out << " }\n" << "}\n"; } #undef V_TO_RV #undef RV_TO_V rumur-2020.02.17/rumur/src/generate-stmt.cc000066400000000000000000000237531362265074000203250ustar00rootroot00000000000000#include #include #include "generate.h" #include #include #include "options.h" #include #include #include #include "utils.h" using namespace rumur; static void clear(std::ostream &out, const TypeExpr &t, const std::string &offset = "((size_t)0)", size_t depth = 0) { const std::string indent = std::string(2 * (depth + 1), ' '); if (t.is_simple()) { out << indent << "handle_write_raw(s, (struct handle){ .base = root.base + " << "(root.offset + " << offset << ") / CHAR_BIT, .offset = " << "(root.offset + " << offset << ") % CHAR_BIT, .width = " << t.width() << "ull }, 1);\n"; return; } const Ptr type = t.resolve(); if (auto a = dynamic_cast(type.get())) { // The number of elements in this array as a C code string mpz_class ic = a->index_type->count() - 1; const std::string ub = "((size_t)" + ic.get_str() + "ull)"; // The bit size of each array element as a C code string const std::string width = "((size_t)" + a->element_type->width().get_str() + "ull)"; // Generate a loop to iterate over all the elements const std::string var = "i" + std::to_string(depth); out << indent << "for (size_t " << var << " = 0; " << var << " < " << ub << "; " << var << "++) {\n"; // Generate code to clear each element const std::string off = offset + " + " + var + " * " + width; clear(out, *a->element_type, off, depth + 1); // Close the loop out << indent << "}\n"; return; } if (auto r = dynamic_cast(type.get())) { std::string off = offset; for (const Ptr &f : r->fields) { // Generate code to clear this field clear(out, *f->type, off, depth); // Jump over this field to get the offset of the next field const std::string width = "((size_t)" + f->type->width().get_str() + "ull)"; off += " + " + width; } return; } assert(!"unreachable"); } namespace { class Generator : public ConstStmtTraversal { private: std::ostream *out; public: Generator(std::ostream &o): out(&o) { } void visit_aliasstmt(const AliasStmt &s) final { *out << " {\n"; for (auto &a : s.aliases) { *out << " "; generate_decl(*out, *a); *out << ";\n"; } for (auto &st : s.body) { *out << " "; generate_stmt(*out, *st); *out << ";\n"; } *out << " }\n"; } void visit_assignment(const Assignment &s) final { if (s.lhs->type()->is_simple()) { const std::string lb = s.lhs->type()->lower_bound(); const std::string ub = s.lhs->type()->upper_bound(); *out << "handle_write(" << to_C_string(s.loc) << ", rule_name, " << to_C_string(*s.lhs) << ", s, " << lb << ", " << ub << ", "; generate_lvalue(*out, *s.lhs); *out << ", "; generate_rvalue(*out, *s.rhs); *out << ")"; } else { *out << "handle_copy("; generate_lvalue(*out, *s.lhs); *out << ", "; generate_rvalue(*out, *s.rhs); *out << ")"; } } void visit_clear(const Clear &s) final { *out << "do {\n" << " struct handle root = "; generate_lvalue(*out, *s.rhs); *out << ";\n"; clear(*out, *s.rhs->type()); *out << "} while (0)"; } void visit_errorstmt(const ErrorStmt &s) final { *out << "error(s, \"%s\", \"" << escape(s.message) << "\")"; } void visit_for(const For &s) final { generate_quantifier_header(*out, s.quantifier); for (auto &st : s.body) { *out << " "; generate_stmt(*out, *st); *out << ";\n"; } generate_quantifier_footer(*out, s.quantifier); } void visit_if(const If &s) final { bool first = true; for (const IfClause &c : s.clauses) { /* HACK: Equality comparisons against by-value function/procedure * parameters of simple type can result in code generation like: * * if ((ru_x == ...)) { * * On compilers with -Wparentheses-equality (e.g. Apple's Clang 10.0.0) * this generates a spurious warning. To avoid this, we suppress the * duplicate brackets for any comparison using a binary operator. */ bool needs_bracketing = !isa(c.condition); // equality comparison with complex types does not cause double bracketing if (c.condition != nullptr) { if (auto e = dynamic_cast(&*c.condition)) { if (!e->lhs->type()->is_simple()) needs_bracketing = true; } if (auto e = dynamic_cast(&*c.condition)) { if (!e->lhs->type()->is_simple()) needs_bracketing = true; } } if (!first) *out << "else "; if (c.condition != nullptr) { *out << "if "; if (needs_bracketing) *out << "("; generate_rvalue(*out, *c.condition); if (needs_bracketing) *out << ") "; } *out << " {\n"; for (auto &st : c.body) { generate_stmt(*out, *st); *out << ";\n"; } *out << "}\n"; first = false; } } void visit_procedurecall(const ProcedureCall &s) final { generate_rvalue(*out, s.call); } void visit_propertystmt(const PropertyStmt &s) final { switch (s.property.category) { case Property::ASSERTION: *out << "if (__builtin_expect(!"; generate_property(*out, s.property); *out << ", 0)) {\nerror(s, \"Assertion failed: %s:" << s.loc << ": %s\", \"" << escape(input_filename) << "\", "; if (s.message == "") { /* Assertion has no associated text. Use the expression itself * instead. */ *out << to_C_string(*s.property.expr); } else { *out << "\"" << escape(s.message) << "\""; } *out << ");\n}"; break; case Property::ASSUMPTION: *out << "if (__builtin_expect(!"; generate_property(*out, s.property); *out << ", 0)) {\n" << " assert(JMP_BUF_NEEDED && \"longjmping without a setup jmp_buf\");\n" << " siglongjmp(checkpoint, 1);\n" << "}"; break; case Property::COVER: *out << "if ("; generate_property(*out, s.property); *out << ") {\n" << " (void)__atomic_fetch_add(&covers[COVER_" << s.property.unique_id << "], 1, __ATOMIC_SEQ_CST);\n" << "}"; break; case Property::LIVENESS: assert(s.property.category != Property::LIVENESS && "liveness property " "illegally appearing in statement instead of at the top level"); break; } } void visit_put(const Put &s) final { if (s.expr == nullptr) { *out << "printf(\"%s\", \"" << s.value << "\")"; return; } if (s.expr->is_lvalue()) { // Construct a string containing a handle to this expression std::ostringstream buffer; generate_lvalue(buffer, *s.expr); generate_print(*out, *s.expr->type(), escape(s.expr->to_string()), buffer.str(), false, false); return; } assert(s.expr->type()->is_simple() && "complex non-lvalue in put statement"); const Ptr type = s.expr->type()->resolve(); if (auto e = dynamic_cast(type.get())) { *out << "{\n" << " value_t v = "; generate_rvalue(*out, *s.expr); *out << ";\n"; size_t i = 0; for (const std::pair &m : e->members) { *out << " "; if (i != 0) *out << "else "; *out << "if (v == " << i << ") {\n" << " printf(\"%s\", \"" << m.first << "\");\n" << " }\n"; i++; } if (!e->members.empty()) *out << "else {\n" << " assert(\"illegal value read from enum expression\");\n" << "}\n"; *out << "}"; return; } *out << "printf(\"%\" PRIVAL, value_to_string("; generate_rvalue(*out, *s.expr); *out << "))"; } void visit_return(const Return &s) final { if (s.expr == nullptr) { *out << "return true"; } else { if (s.expr->type()->is_simple()) { *out << "return "; generate_rvalue(*out, *s.expr); } else { /* The caller will have passed us a handle 'ret' to memory they have * allocated. Copy into it now. */ *out << "do {\n" << " handle_copy(ret, "; generate_rvalue(*out, *s.expr); *out << ");\n" << " return ret;\n" << "} while (0)"; } } } void visit_switch(const Switch &s) final { *out << "do {\n" // Mark the following unused in case there are no cases << " value_t v __attribute__((unused)) = "; generate_rvalue(*out, *s.expr); *out << ";\n"; bool first_case = true; for (const SwitchCase &c : s.cases) { *out << " "; if (!first_case) *out << "else "; if (!c.matches.empty()) { *out << "if ("; bool first_match = true; for (auto &m : c.matches) { if (!first_match) *out << " || "; *out << "v == "; generate_rvalue(*out, *m); first_match = false; } *out << ") "; } *out << "{\n"; for (auto &st : c.body) { *out << " "; dispatch(*st); *out << ";\n"; } *out << " }\n"; first_case = false; } *out << "} while (0)"; } void visit_undefine(const Undefine &s) final { *out << "handle_zero("; generate_lvalue(*out, *s.rhs); *out << ")"; } void visit_while(const While &s) final { *out << "while ("; generate_rvalue(*out, *s.condition); *out << ") {\n"; for (auto &st : s.body) { *out << " "; generate_stmt(*out, *st); *out << ";\n"; } *out << "}"; } virtual ~Generator() = default; }; } void generate_stmt(std::ostream &out, const Stmt &s) { Generator g(out); g.dispatch(s); } rumur-2020.02.17/rumur/src/generate.h000066400000000000000000000031361362265074000171730ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include "ValueType.h" #include int output_checker(const std::string &path, const rumur::Model &model, const std::pair &value_types); // Generate prelude definitions to allocate memory for function returns void generate_allocations(std::ostream &out, const rumur::Stmt &stmt); // Helper for calling the above on a body of functions void generate_allocations(std::ostream &out, const std::vector> &stmts); // Generate definition of a ConstDecl or VarDecl void generate_decl(std::ostream &out, const rumur::Decl &d); void generate_function(std::ostream &out, const rumur::Function &f, const std::vector> &decls); void generate_model(std::ostream &out, const rumur::Model &m); // Generate C code to print the value of the given type at the given handle. void generate_print(std::ostream &out, const rumur::TypeExpr &e, const std::string &prefix, const std::string &handle, bool support_diff, bool support_xml); void generate_property(std::ostream &out, const rumur::Property &p); void generate_lvalue(std::ostream &out, const rumur::Expr &e); void generate_rvalue(std::ostream &out, const rumur::Expr &e); void generate_quantifier_header(std::ostream &out, const rumur::Quantifier &q); void generate_quantifier_footer(std::ostream &out, const rumur::Quantifier &q); void generate_stmt(std::ostream &out, const rumur::Stmt &s); void generate_cover_array(std::ostream &out, const rumur::Model &model); rumur-2020.02.17/rumur/src/has-start-state.cc000066400000000000000000000006001362265074000205540ustar00rootroot00000000000000#include #include "has-start-state.h" #include using namespace rumur; namespace { class Finder : public ConstTraversal { public: bool result = false; void visit_startstate(const StartState&) final { result = true; } virtual ~Finder() = default; }; } bool has_start_state(const Model &m) { Finder f; f.dispatch(m); return f.result; } rumur-2020.02.17/rumur/src/has-start-state.h000066400000000000000000000002321362265074000204170ustar00rootroot00000000000000#pragma once #include #include // does this model have at least one start state? bool has_start_state(const rumur::Model &m); rumur-2020.02.17/rumur/src/log.cc000066400000000000000000000016671362265074000163270ustar00rootroot00000000000000#include #include #include "log.h" #include "options.h" #include // A buffer that discards data written to it namespace { class NullBuf : public std::streambuf { public: int overflow(int c) { return c; } }; } // A stream that discards data written to it NullBuf null_buf; std::ostream null(&null_buf); std::ostream *debug = &null; std::ostream *info = &null; std::ostream *warn = &std::cerr; void set_log_level(LogLevel level) { switch (level) { case LogLevel::SILENT: debug = &null; info = &null; warn = &null; break; case LogLevel::WARNINGS: debug = &null; info = &null; warn = &std::cerr; break; case LogLevel::INFO: debug = &null; info = &std::cerr; warn = &std::cerr; break; case LogLevel::DEBUG: debug = &std::cerr; info = &std::cerr; warn = &std::cerr; break; } } rumur-2020.02.17/rumur/src/log.h000066400000000000000000000003021362265074000161520ustar00rootroot00000000000000#pragma once #include #include #include "options.h" void set_log_level(LogLevel level); extern std::ostream *debug; extern std::ostream *info; extern std::ostream *warn; rumur-2020.02.17/rumur/src/main.cc000066400000000000000000000510441362265074000164640ustar00rootroot00000000000000#include #include #include #include #include #include #include "environ.h" #include #include "generate.h" #include #include "has-start-state.h" #include "../../common/help.h" #include #include "log.h" #include #include "options.h" #include "resources.h" #include #include "smt/except.h" #include "smt/simplify.h" #include #include #include #include #include #include #include #include "utils.h" #include "ValueType.h" using namespace rumur; static std::shared_ptr in; static std::shared_ptr out; static unsigned string_to_percentage(const std::string &s) { int p; try { p = std::stoi(s); if (p < 1 || p > 100) throw std::invalid_argument(""); } catch (std::out_of_range&) { throw std::invalid_argument(""); } return (unsigned)p; } static void parse_args(int argc, char **argv) { for (;;) { enum { OPT_BOUND = 128, OPT_COLOUR, OPT_COUNTEREXAMPLE_TRACE, OPT_DEADLOCK_DETECTION, OPT_MAX_ERRORS, OPT_MONOPOLISE, OPT_OUTPUT_FORMAT, OPT_PACK_STATE, OPT_SANDBOX, OPT_SMT_ARG, OPT_SMT_BITVECTORS, OPT_SMT_BUDGET, OPT_SMT_LOGIC, OPT_SMT_PATH, OPT_SMT_PRELUDE, OPT_SMT_SIMPLIFICATION, OPT_SYMMETRY_REDUCTION, OPT_TRACE, OPT_VALUE_TYPE, OPT_VERSION, }; static struct option opts[] = { { "bound", required_argument, 0, OPT_BOUND }, { "color", required_argument, 0, OPT_COLOUR }, { "colour", required_argument, 0, OPT_COLOUR }, { "counterexample-trace", required_argument, 0, OPT_COUNTEREXAMPLE_TRACE }, { "deadlock-detection", required_argument, 0, OPT_DEADLOCK_DETECTION }, { "debug", no_argument, 0, 'd' }, { "help", no_argument, 0, 'h' }, { "max-errors", required_argument, 0, OPT_MAX_ERRORS }, { "monopolise", no_argument, 0, OPT_MONOPOLISE }, { "monopolize", no_argument, 0, OPT_MONOPOLISE }, { "output", required_argument, 0, 'o' }, { "output-format", required_argument, 0, OPT_OUTPUT_FORMAT }, { "pack-state", required_argument, 0, OPT_PACK_STATE }, { "quiet", no_argument, 0, 'q' }, { "sandbox", required_argument, 0, OPT_SANDBOX }, { "set-capacity", required_argument, 0, 's' }, { "set-expand-threshold", required_argument, 0, 'e' }, { "smt-arg", required_argument, 0, OPT_SMT_ARG }, { "smt-bitvectors", required_argument, 0, OPT_SMT_BITVECTORS }, { "smt-budget", required_argument, 0, OPT_SMT_BUDGET }, { "smt-logic", required_argument, 0, OPT_SMT_LOGIC }, { "smt-path", required_argument, 0, OPT_SMT_PATH }, { "smt-prelude", required_argument, 0, OPT_SMT_PRELUDE }, { "smt-simplification", required_argument, 0, OPT_SMT_SIMPLIFICATION }, { "symmetry-reduction", required_argument, 0, OPT_SYMMETRY_REDUCTION }, { "threads", required_argument, 0, 't' }, { "trace", required_argument, 0, OPT_TRACE }, { "value-type", required_argument, 0, OPT_VALUE_TYPE }, { "verbose", no_argument, 0, 'v' }, { "version", no_argument, 0, OPT_VERSION }, { 0, 0, 0, 0 }, }; int option_index = 0; int c = getopt_long(argc, argv, "de:ho:qs:t:v", opts, &option_index); if (c == -1) break; switch (c) { case 'd': // --debug options.log_level = LogLevel::DEBUG; set_log_level(options.log_level); break; case 'e': { // --set-expand-threshold ... bool valid = true; try { options.set_expand_threshold = string_to_percentage(optarg); } catch (std::invalid_argument&) { valid = false; } if (!valid) { std::cerr << "invalid --set-expand-threshold argument \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; } case 'h': // --help help(doc_rumur_1, doc_rumur_1_len); exit(EXIT_SUCCESS); case 'o': // --output ... out = std::make_shared(optarg); break; case 'q': // --quiet options.log_level = LogLevel::SILENT; set_log_level(options.log_level); break; case 's': { // --set-capacity ... bool valid = true; try { options.set_capacity = optarg; if (options.set_capacity <= 0) valid = false; } catch (std::invalid_argument&) { valid = false; } if (!valid) { std::cerr << "invalid --set-capacity argument \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; } case 't': { // --threads ... bool valid = true; try { options.threads = optarg; if (options.threads < 0) valid = false; } catch (std::invalid_argument&) { valid = false; } if (!valid) { std::cerr << "invalid --threads argument \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; } case 'v': // --verbose options.log_level = LogLevel::INFO; set_log_level(options.log_level); break; case '?': std::cerr << "run `" << argv[0] << " --help` to see available options\n"; exit(EXIT_FAILURE); case OPT_COLOUR: // --colour ... if (strcmp(optarg, "auto") == 0) { options.color = Color::AUTO; } else if (strcmp(optarg, "on") == 0) { if (options.machine_readable_output) { std::cerr << "colour is not supported in combination with " << "--output-format \"machine readable\"\n"; exit(EXIT_FAILURE); } options.color = Color::ON; } else if (strcmp(optarg, "off") == 0) { options.color = Color::OFF; } else { std::cerr << "invalid --colour argument \"" << optarg << "\"\n" << "valid arguments are \"auto\", \"off\", and \"on\"\n"; exit(EXIT_FAILURE); } break; case OPT_TRACE: // --trace ... if (strcmp(optarg, "handle_reads") == 0) { options.traces |= TC_HANDLE_READS; } else if (strcmp(optarg, "handle_writes") == 0) { options.traces |= TC_HANDLE_WRITES; } else if (strcmp(optarg, "memory_usage") == 0) { options.traces |= TC_MEMORY_USAGE; } else if (strcmp(optarg, "queue") == 0) { options.traces |= TC_QUEUE; } else if (strcmp(optarg, "set") == 0) { options.traces |= TC_SET; } else if (strcmp(optarg, "symmetry_reduction") == 0) { options.traces |= TC_SYMMETRY_REDUCTION; } else if (strcmp(optarg, "all") == 0) { options.traces = uint64_t(-1); } else { std::cerr << "invalid --trace argument \"" << optarg << "\"\n" << "valid arguments are \"handle_reads\", \"handle_writes\", " "\"memory_usage\", \"queue\", \"set\", and " "\"symmetry_reduction\"\n"; exit(EXIT_FAILURE); } break; case OPT_DEADLOCK_DETECTION: // --deadlock-detection ... if (strcmp(optarg, "off") == 0) { options.deadlock_detection = DeadlockDetection::OFF; } else if (strcmp(optarg, "stuck") == 0) { options.deadlock_detection = DeadlockDetection::STUCK; } else if (strcmp(optarg, "stuttering") == 0) { options.deadlock_detection = DeadlockDetection::STUTTERING; } else { std::cerr << "invalid argument to --deadlock-detection, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_MONOPOLISE: { // --monopolise long pagesize = sysconf(_SC_PAGESIZE); if (pagesize < 0) { perror("failed to retrieve page size"); exit(EXIT_FAILURE); } long physpages = sysconf(_SC_PHYS_PAGES); if (physpages < 0) { perror("failed to retrieve physical pages"); exit(EXIT_FAILURE); } /* Allocate a set that will eventually cover all of memory upfront. Note * that this will never actually reach 100% occupancy because memory * also needs to contain our code and data as well as the OS. */ options.set_capacity = size_t(pagesize) * size_t(physpages); // Never expand the set. options.set_expand_threshold = 100; break; } case OPT_PACK_STATE: // --pack-state ... if (strcmp(optarg, "on") == 0) { options.pack_state = true; } else if (strcmp(optarg, "off") == 0) { options.pack_state = false; } else { std::cerr << "invalid argument to --pack_state, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_SYMMETRY_REDUCTION: // --symmetry-reduction ... if (strcmp(optarg, "off") == 0) { options.symmetry_reduction = SymmetryReduction::OFF; } else if (strcmp(optarg, "heuristic") == 0) { options.symmetry_reduction = SymmetryReduction::HEURISTIC; } else if (strcmp(optarg, "exhaustive") == 0) { options.symmetry_reduction = SymmetryReduction::EXHAUSTIVE; } else { std::cerr << "invalid argument to --symmetry-reduction, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_SANDBOX: // --sandbox ... if (strcmp(optarg, "on") == 0) { options.sandbox_enabled = true; } else if (strcmp(optarg, "off") == 0) { options.sandbox_enabled = false; } else { std::cerr << "invalid argument to --sandbox, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_MAX_ERRORS: { // --max-errors ... bool valid = true; try { options.max_errors = optarg; if (options.max_errors <= 0) valid = false; } catch (std::invalid_argument&) { valid = false; } if (!valid) { std::cerr << "invalid --max-errors argument \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; } case OPT_COUNTEREXAMPLE_TRACE: // --counterexample-trace ... if (strcmp(optarg, "full") == 0) { options.counterexample_trace = CounterexampleTrace::FULL; } else if (strcmp(optarg, "diff") == 0) { options.counterexample_trace = CounterexampleTrace::DIFF; } else if (strcmp(optarg, "off") == 0) { options.counterexample_trace = CounterexampleTrace::OFF; } else { std::cerr << "invalid argument to --counterexample-trace, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_OUTPUT_FORMAT: // --output-format ... if (strcmp(optarg, "machine-readable") == 0) { options.machine_readable_output = true; // Disable colour that would interfere with XML options.color = Color::OFF; } else if (strcmp(optarg, "human-readable") == 0) { options.machine_readable_output = false; } else { std::cerr << "invalid argument to --output-format, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; case OPT_VERSION: // --version std::cout << "Rumur version " << get_version() << "\n"; exit(EXIT_SUCCESS); case OPT_BOUND: { // --bound ... bool valid = true; try { options.bound = optarg; if (options.bound < 0) valid = false; } catch (std::invalid_argument&) { valid = false; } if (!valid) { std::cerr << "invalid --bound argument \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; } case OPT_VALUE_TYPE: // --value-type ... options.value_type = optarg; break; case OPT_SMT_ARG: // --smt-arg ... options.smt.args.emplace_back(optarg); if (options.smt.simplification == SmtSimplification::AUTO) { options.smt.simplification = SmtSimplification::ON; } break; case OPT_SMT_BITVECTORS: // --smt-bitvectors ... if (strcmp(optarg, "on") == 0) { options.smt.use_bitvectors = true; } else if (strcmp(optarg, "off") == 0) { options.smt.use_bitvectors = false; } else { std::cerr << "invalid argument to --smt-bitvectors, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } if (options.smt.simplification == SmtSimplification::AUTO) { options.smt.simplification = SmtSimplification::ON; } break; case OPT_SMT_BUDGET: { // --smt-budget ... bool valid = true; try { options.smt.budget = optarg; if (options.smt.budget < 0) valid = false; } catch (std::invalid_argument&) { valid = false; } if (!valid) { std::cerr << "invalid --smt-budget, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } if (options.smt.simplification == SmtSimplification::AUTO) { options.smt.simplification = SmtSimplification::ON; } break; } case OPT_SMT_LOGIC: // --smt-logic ... options.smt.logic = optarg; if (options.smt.simplification == SmtSimplification::AUTO) { options.smt.simplification = SmtSimplification::ON; } *warn << "the option --smt-logic is deprecated\n"; break; case OPT_SMT_PATH: // --smt-path ... options.smt.path = optarg; if (options.smt.simplification == SmtSimplification::AUTO) { options.smt.simplification = SmtSimplification::ON; } break; case OPT_SMT_PRELUDE: // --smt-prelude ... options.smt.prelude.emplace_back(optarg); if (options.smt.simplification == SmtSimplification::AUTO) { options.smt.simplification = SmtSimplification::ON; } break; case OPT_SMT_SIMPLIFICATION: // --smt-simplification ... if (strcmp(optarg, "on") == 0) { options.smt.simplification = SmtSimplification::ON; } else if (strcmp(optarg, "off") == 0) { options.smt.simplification = SmtSimplification::OFF; } else { std::cerr << "invalid argument to --smt-simplification, \"" << optarg << "\"\n"; exit(EXIT_FAILURE); } break; default: std::cerr << "unexpected error\n"; exit(EXIT_FAILURE); } } if (optind == argc - 1) { struct stat buf; if (stat(argv[optind], &buf) < 0) { std::cerr << "failed to open " << argv[optind] << ": " << strerror(errno) << "\n"; exit(EXIT_FAILURE); } if (S_ISDIR(buf.st_mode)) { std::cerr << "failed to open " << argv[optind] << ": this is a directory\n"; exit(EXIT_FAILURE); } auto inf = std::make_shared(argv[optind]); if (!inf->is_open()) { std::cerr << "failed to open " << argv[optind] << "\n"; exit(EXIT_FAILURE); } input_filename = argv[optind]; in = inf; } if (out == nullptr) { std::cerr << "output file is required\n"; exit(EXIT_FAILURE); } if (options.threads == 0) { // automatic long r = sysconf(_SC_NPROCESSORS_ONLN); if (r < 1) { options.threads = 1; } else { options.threads = r; } } if (options.smt.simplification == SmtSimplification::ON && options.smt.path == "") { *warn << "SMT simplification was enabled but no path was provided to the " << "solver (--smt-path ...), so it will be disabled\n"; options.smt.simplification = SmtSimplification::OFF; } } static bool use_colors() { return options.color == Color::ON || (options.color == Color::AUTO && isatty(STDERR_FILENO)); } static std::string bold() { if (use_colors()) return "\033[1m"; return ""; } static std::string green() { if (use_colors()) return "\033[32m"; return ""; } static std::string red() { if (use_colors()) return "\033[31m"; return ""; } static std::string reset() { if (use_colors()) return "\033[0m"; return ""; } static std::string white() { if (use_colors()) return "\033[37m"; return ""; } static void print_location(const std::string &file, const location &location) { // don't try to open stdin (see default in options.cc) if (file == "") return; std::ifstream f(file); if (!f.is_open()) { // ignore failure here as it's non-critical return; } // the type of position.line and position.column changes across Bison // releases, so avoid some -Wsign-compare warnings by casting them in advance auto loc_line = static_cast(location.begin.line); auto loc_col = static_cast(location.begin.column); std::string line; unsigned long lineno = 0; while (lineno < loc_line) { if (!std::getline(f, line)) return; lineno++; } // print the line, and construct an underline indicating the column location std::ostringstream buf; unsigned long col = 1; for (const char &c : line) { if (col == loc_col) { buf << green() << bold() << "^" << reset(); } else if (col < loc_col) { if (c == '\t') { buf << '\t'; } else { buf << ' '; } } std::cerr << c; col++; } std::cerr << "\n"; std::cerr << buf.str() << "\n"; } int main(int argc, char **argv) { // Parse command line options parse_args(argc, argv); // Parse input model Ptr m; try { m = parse(in == nullptr ? std::cin : *in); } catch (Error &e) { std::cerr << white() << bold() << input_filename << ":" << e.loc << ":" << reset() << " " << red() << bold() << "error:" << reset() << " " << white() << bold() << e.what() << reset() << "\n"; print_location(input_filename, e.loc); return EXIT_FAILURE; } assert(m != nullptr); /* Re-index the model (assign unique identifiers to each node that are used in * generation of the verifier). */ m->reindex(); // resolve symbolic references and validate the model try { resolve_symbols(*m); validate(*m); } catch (Error &e) { std::cerr << white() << bold() << input_filename << ":" << e.loc << ":" << reset() << " " << red() << bold() << "error:" << reset() << " " << white() << bold() << e.what() << reset() << "\n"; print_location(input_filename, e.loc); return EXIT_FAILURE; } // Check whether we have a start state. if (!has_start_state(*m)) *warn << "warning: model has no start state\n"; // run SMT simplification if the user enabled it if (options.smt.simplification == SmtSimplification::ON) { try { smt::simplify(*m); } catch (smt::BudgetExhausted&) { *info << "SMT solver budget (" << options.smt.budget << "ms) exhausted\n"; } catch (smt::Unsupported &e) { if (e.expr != nullptr) *info << e.expr->loc << ": "; *info << e.what() << "\n"; } } // get value_t to use in the checker std::pair value_types; try { value_types = get_value_type(options.value_type, *m); } catch (std::runtime_error &e) { std::cerr << "invalid --value-type " << options.value_type << ": " << e.what() << "\n"; return EXIT_FAILURE; } assert(out != nullptr); if (output_checker(*out, *m, value_types) != 0) return EXIT_FAILURE; #ifndef __AFL_COMPILER #define __AFL_COMPILER 0 #endif if (__AFL_COMPILER) { // extra steps for when we're being fuzzed // find the C compiler const char *cc = getenv("CC"); if (cc == nullptr) cc = "cc"; // setup an argument vector for calling the C compiler const char *args[] = { cc, "-std=c11", "-x", "c", "-o", "/dev/null", "-Werror=format", "-Werror=sign-compare", "-Werror=type-limits", out->c_str(), #ifdef __x86_64__ "-mcx16", #endif "-lpthread" }; // start the C compiler int r = posix_spawnp(nullptr, args[0], nullptr, nullptr, const_cast(args), get_environ()); if (r != 0) { std::cerr << "posix_spawnp failed: " << strerror(r) << "\n"; abort(); } // wait for it to finish int stat_loc; pid_t pid = wait(&stat_loc); if (pid == -1) { std::cerr << "wait failed\n"; abort(); } // if it terminated abnormally, pass an error up to the fuzzer if (WIFSIGNALED(stat_loc) || WIFSTOPPED(stat_loc)) { std::cerr << "subprocess either signalled or stopped\n"; abort(); } assert(WIFEXITED(stat_loc)); } return EXIT_SUCCESS; } rumur-2020.02.17/rumur/src/max-simple-width.cc000066400000000000000000000034321362265074000207270ustar00rootroot00000000000000#include #include #include "max-simple-width.h" #include #include using namespace rumur; // An AST traversal that learns the maximum simple type width. namespace { class Measurer : public ConstTraversal { public: mpz_class max = 0; /* Nothing required for complex types, but we do need to descend into their * children. */ void visit_array(const Array &n) final { dispatch(*n.index_type); dispatch(*n.element_type); } void visit_enum(const Enum &n) final { mpz_class w = n.width(); if (w > max) max = w; } // we override visit_quantifier in order to also descend into the quantifier’s // decl that the generic traversal logic assumes you do not want to do void visit_quantifier(const Quantifier &n) final { if (n.type != nullptr) dispatch(*n.type); if (n.from != nullptr) dispatch(*n.from); if (n.to != nullptr) dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); dispatch(*n.decl); } void visit_range(const Range &n) final { mpz_class w = n.width(); if (w > max) max = w; } void visit_record(const Record &n) final { for (auto &f : n.fields) dispatch(*f); } void visit_scalarset(const Scalarset &n) final { mpz_class w = n.width(); if (w > max) max = w; } void visit_typeexprid(const TypeExprID &n) final { if (n.is_simple()) { mpz_class w = n.width(); if (w > max) max = w; } /* We don't need to descend into any children, because the referent will * either (a) already have been encountered or (b) be a built-in like * 'boolean' that is a simple type. */ } }; } mpz_class max_simple_width(const Model &m) { Measurer t; t.dispatch(m); return t.max; } rumur-2020.02.17/rumur/src/max-simple-width.h000066400000000000000000000014371362265074000205740ustar00rootroot00000000000000#pragma once #include #include #include /** Get the greatest width of any simple type used in a model. * * It may not immediately be clear why you would care about such a property. * However, note that the only handles that are ever actually *read* or * *written* in the generated checker are those for simple typed values. With * this in mind, we can conclude that handle_read_raw and handle_write_raw only * ever need to deal with handles of at most this width. Knowing this value * statically ahead of time enables some interesting optimisations. * * Returns 0 if there are no simple types in the model. This case should only * occur if the state is empty; i.e. if the model has no variables. */ mpz_class max_simple_width(const rumur::Model &m); rumur-2020.02.17/rumur/src/options.cc000066400000000000000000000002601362265074000172250ustar00rootroot00000000000000#include #include "options.h" #include Options options; // default value of "" may be overwritten by main() std::string input_filename = ""; rumur-2020.02.17/rumur/src/options.h000066400000000000000000000051461362265074000170770ustar00rootroot00000000000000#pragma once #include #include #include #include #include enum struct Color { OFF, ON, AUTO, }; enum trace_category_t { TC_HANDLE_READS = 0x1, TC_HANDLE_WRITES = 0x2, TC_MEMORY_USAGE = 0x4, TC_QUEUE = 0x8, TC_SET = 0x10, TC_SYMMETRY_REDUCTION = 0x20, }; enum struct LogLevel { SILENT, WARNINGS, INFO, DEBUG, }; enum struct DeadlockDetection { OFF, STUCK, STUTTERING, }; enum struct CounterexampleTrace { OFF, DIFF, FULL, }; enum struct SymmetryReduction { OFF, HEURISTIC, EXHAUSTIVE, }; enum struct SmtSimplification { OFF, ON, AUTO, }; struct Options { mpz_class threads = 0; LogLevel log_level = LogLevel::WARNINGS; mpz_class set_capacity = 8 * 1024 * 1024; /* Limit (percentage occupancy) at which we expand the capacity of the state * set. */ unsigned set_expand_threshold = 75; // Whether to use ANSI colour codes in the checker's output. Color color = Color::AUTO; // Bitmask of enabled tracing uint64_t traces = 0; // Deadlock detection enabled? DeadlockDetection deadlock_detection = DeadlockDetection::STUTTERING; // Symmetry reduction enabled? SymmetryReduction symmetry_reduction = SymmetryReduction::HEURISTIC; // Use OS mechanisms to sandbox the checker? bool sandbox_enabled = false; // Number of errors to report before exiting. mpz_class max_errors = 1; // How to print counterexample traces CounterexampleTrace counterexample_trace = CounterexampleTrace::DIFF; // Print output as XML? bool machine_readable_output = false; // Limit for exploration. 0 means unbounded. mpz_class bound = 0; // Type used for value_t in the checker std::string value_type = "auto"; // whether to bit-pack members of the state struct bool pack_state = true; // options related to SMT solver interaction struct { // Path to SMT solver. "" indicates we have no solver. std::string path; // arguments to pass to SMT solver when calling it std::vector args; // total SMT solver execution time allowed in milliseconds mpz_class budget = 30000; // use SMT solver for expression simplification? SmtSimplification simplification = SmtSimplification::AUTO; // SMTLIB logic to use when building problems std::string logic; // text to emit to the solver prior to a SAT problem std::vector prelude; // use BitVecs instead of Ints? bool use_bitvectors = false; } smt; }; extern Options options; // input model's path extern std::string input_filename; rumur-2020.02.17/rumur/src/output.cc000066400000000000000000000154661362265074000171100ustar00rootroot00000000000000#include "assume-statements-count.h" #include #include #include #include #include "generate.h" #include "max-simple-width.h" #include "options.h" #include "resources.h" #include #include #include #include "utils.h" #include "ValueType.h" using namespace rumur; static std::ostream &operator<<(std::ostream &out, Color c) { switch (c) { case Color::OFF: out << "OFF"; break; case Color::ON: out << "ON"; break; case Color::AUTO: out << "AUTO"; break; } return out; } static std::ostream &operator<<(std::ostream &out, DeadlockDetection d) { switch (d) { case DeadlockDetection::OFF: out << "DEADLOCK_DETECTION_OFF"; break; case DeadlockDetection::STUCK: out << "DEADLOCK_DETECTION_STUCK"; break; case DeadlockDetection::STUTTERING: out << "DEADLOCK_DETECTION_STUTTERING"; break; } return out; } static std::ostream &operator<<(std::ostream &out, SymmetryReduction s) { switch (s) { case SymmetryReduction::OFF: out << "SYMMETRY_REDUCTION_OFF"; break; case SymmetryReduction::HEURISTIC: out << "SYMMETRY_REDUCTION_HEURISTIC"; break; case SymmetryReduction::EXHAUSTIVE: out << "SYMMETRY_REDUCTION_EXHAUSTIVE"; break; } return out; } static std::ostream &operator<<(std::ostream &out, CounterexampleTrace c) { switch (c) { case CounterexampleTrace::OFF: out << "CEX_OFF"; break; case CounterexampleTrace::DIFF: out << "DIFF"; break; case CounterexampleTrace::FULL: out << "FULL"; break; } return out; } // maximum value state.rule_taken can reach in the generated checker, given the // guarding predicate static mpz_class rule_taken_max(const Model &model, bool (*predicate)(const Rule &r)) { mpz_class total = 0; // flatten all rules for (const Ptr &r : model.rules) { const std::vector> flattened = r->flatten(); for (const Ptr &f : flattened) { // skip anything that does not match our criteria if (!predicate(*f)) continue; // determine how many rules this generates when quantifiers are expanded mpz_class rules = 1; for (const Quantifier &q : f->quantifiers) { assert(q.constant() && "non-constant quantifier used in rule " "(unvalidated AST?)"); rules *= q.count(); } total += rules; } } return total; } // state.rule_taken is overloaded to mean different things depending on whether // the containing state is an initial state or a subsequent one, so we need to // compute the maximum value either of these can induce static mpz_class rule_taken_max_start(const Model &model) { auto is_start = [](const Rule &r){ return isa(&r); }; return rule_taken_max(model, is_start); } static mpz_class rule_taken_max_rule(const Model &model) { auto is_rule = [](const Rule &r) { return isa(&r); }; return rule_taken_max(model, is_rule); } // maximum value needed to be representable in state.rule_taken, accounting for // configuration static mpz_class rule_taken_limit(const Model &model) { // rule taken data is only required when we need counter-example traces if (options.counterexample_trace == CounterexampleTrace::OFF) return 0; mpz_class s_max = rule_taken_max_start(model); mpz_class r_max = rule_taken_max_rule(model); return s_max > r_max ? s_max : r_max; } int output_checker(const std::string &path, const Model &model, const std::pair &value_types) { std::ofstream out(path); if (!out) return -1; if (options.log_level < LogLevel::DEBUG) out << "#define NDEBUG 1\n\n"; out // #includes << std::string((const char*)resources_includes_c, resources_includes_c_len) << "\n" // Settings that are used in header.c << "enum { SET_CAPACITY = " << options.set_capacity << "ul };\n\n" << "enum { SET_EXPAND_THRESHOLD = " << options.set_expand_threshold << " };\n\n" << "static const enum { OFF, ON, AUTO } COLOR = " << options.color << ";\n\n" << "enum trace_category_t {\n" << " TC_HANDLE_READS = " << TC_HANDLE_READS << ",\n" << " TC_HANDLE_WRITES = " << TC_HANDLE_WRITES << ",\n" << " TC_MEMORY_USAGE = " << TC_MEMORY_USAGE << ",\n" << " TC_QUEUE = " << TC_QUEUE << ",\n" << " TC_SET = " << TC_SET << ",\n" << " TC_SYMMETRY_REDUCTION = " << TC_SYMMETRY_REDUCTION << ",\n" << "};\n" << "static const uint64_t TRACES_ENABLED = UINT64_C(" << options.traces << ");\n\n" << "static const enum {\n" << " DEADLOCK_DETECTION_OFF,\n" << " DEADLOCK_DETECTION_STUCK,\n" << " DEADLOCK_DETECTION_STUTTERING,\n" << "} DEADLOCK_DETECTION = " << options.deadlock_detection << ";\n\n" << "static const enum {\n" << " SYMMETRY_REDUCTION_OFF,\n" << " SYMMETRY_REDUCTION_HEURISTIC,\n" << " SYMMETRY_REDUCTION_EXHAUSTIVE,\n" << "} SYMMETRY_REDUCTION = " << options.symmetry_reduction << ";\n\n" << "enum { SANDBOX_ENABLED = " << options.sandbox_enabled << " };\n\n" << "enum { MAX_ERRORS = " << options.max_errors << "ul };\n\n" << "enum { THREADS = " << options.threads << "ul };\n\n" << "enum { STATE_SIZE_BITS = " << model.size_bits() << "ul };\n\n" << "enum { ASSUME_STATEMENTS_COUNT = " << assume_statements_count(model) << "ul };\n\n" << "#define LIVENESS_COUNT " << model.liveness_count() << "\n\n" << "#define CEX_OFF 0\n" << "#define DIFF 1\n" << "#define FULL 2\n" << "#define COUNTEREXAMPLE_TRACE " << options.counterexample_trace << "\n\n" << "enum { MACHINE_READABLE_OUTPUT = " << options.machine_readable_output << " };\n\n" << "enum { MAX_SIMPLE_WIDTH = " << max_simple_width(model) << " };\n\n" << "#define BOUND " << options.bound << "\n\n" << "typedef " << value_types.first.c_type << " value_t;\n" << "#define VALUE_MIN " << value_types.first.int_min << "\n" << "#define VALUE_MAX " << value_types.first.int_max << "\n" << "#define VALUE_C(x) " << value_types.first.int_c << "(x)\n" << "#define PRIVAL " << value_types.first.pri << "\n" << "typedef " << value_types.second.c_type << " raw_value_t;\n" << "#define RAW_VALUE_MIN " << value_types.second.int_min << "\n" << "#define RAW_VALUE_MAX " << value_types.second.int_max << "\n" << "#define PRIRAWVAL " << value_types.second.pri << "\n\n" << "#define RULE_TAKEN_LIMIT " << rule_taken_limit(model) << "\n" << "#define PACK_STATE " << (options.pack_state ? 1 : 0) << "\n"; generate_cover_array(out, model); // Static boiler plate code out << std::string((const char*)resources_header_c, resources_header_c_len) << "\n"; // the model itself generate_model(out, model); return 0; } rumur-2020.02.17/rumur/src/process.cc000066400000000000000000000212171362265074000172150ustar00rootroot00000000000000#include #include #include #include #include "environ.h" #include #include #include "process.h" #include #include #include #include #include #include #include #include #include #include // compile this file with -DTEST_PROCESS to build a test harness #ifdef TEST_PROCESS #define debug (&std::cerr) #else #include "log.h" #endif enum { READ_FD = 0, WRITE_FD = 1 }; // pipe through which we'll redirect SIGCHLD notifications static int sigchld_pipe[2] = { -1, -1 }; // signal handler to redirect SIGCHLD into the above pipe static void handler(int sigchld __attribute__((unused))) { assert(sigchld == SIGCHLD && "SIGCHLD handler received something other than " "SIGCHLD"); assert(sigchld_pipe[WRITE_FD] != -1 && "SIGCHLD handler called before " "SIGCHLD pipe has been setup"); ssize_t w __attribute__((unused)) = write(sigchld_pipe[WRITE_FD], "\0", 1); } static bool inited = false; static int init(void) { if (pipe(sigchld_pipe) < 0) { *debug << "failed to create SIGCHLD pipe\n"; goto fail; } // set the SIGCHLD pipe not to block on reading or writing { int r = sigchld_pipe[READ_FD]; int w = sigchld_pipe[WRITE_FD]; if (fcntl(r, F_SETFL, fcntl(r, F_GETFL) | O_NONBLOCK) == -1 || fcntl(w, F_SETFL, fcntl(w, F_GETFL) | O_NONBLOCK) == -1) { *debug << "failed to set SIGCHLD pipe non-blocking\n"; goto fail; } } // register our redirecting signal handler if (signal(SIGCHLD, handler) == SIG_ERR) { *debug << "failed to set SIGCHLD handler: " << strerror(errno) << "\n"; goto fail; } inited = true; return 0; fail: for (int &fd : sigchld_pipe) { if (fd != -1) { (void)close(fd); fd = -1; } } return -1; } static int max(int a, int b) { return a > b ? a : b; } int run(const std::vector &args, const std::string &input, std::string &output) { if (!inited) { if (init() < 0) return -1; } // setup an argument vector std::vector argv; for (const std::string &a : args) argv.push_back(const_cast(a.c_str())); argv.push_back(nullptr); posix_spawn_file_actions_t fa; int in[2] = { -1, -1 }; int out[2] = { -1, -1 }; int ret = -1; std::ostringstream buffered_output; off_t input_offset = 0; int err = 0; err = posix_spawn_file_actions_init(&fa); if (err != 0) { *debug << "failed file_actions_init: " << strerror(err) << "\n"; return -1; } // create some pipes we'll use to communicate with the child if (pipe(in) < 0 || pipe(out) < 0) { *debug << "failed pipe: " << strerror(errno) << "\n"; goto done; } // set the ends the parent (us) will use as non-blocking if (fcntl(in[WRITE_FD], F_SETFL, fcntl(in[WRITE_FD], F_GETFL) | O_NONBLOCK) == -1 || fcntl(out[READ_FD], F_SETFL, fcntl(out[READ_FD], F_GETFL) | O_NONBLOCK) == -1) { *debug << "failed to set O_NONBLOCK: " << strerror(errno) << "\n"; goto done; } // have the child close the ends it won't need err = posix_spawn_file_actions_addclose(&fa, in[WRITE_FD]); if (err == 0) err = posix_spawn_file_actions_addclose(&fa, out[READ_FD]); if (err != 0) { *debug << "failed file_actions_addclose: " << strerror(err) << "\n"; goto done; } // replace the child's stdin, stdout and stderr with the pipes err = posix_spawn_file_actions_adddup2(&fa, in[READ_FD], STDIN_FILENO); if (err == 0) err = posix_spawn_file_actions_adddup2(&fa, out[WRITE_FD], STDOUT_FILENO); if (err == 0) err = posix_spawn_file_actions_adddup2(&fa, out[WRITE_FD], STDERR_FILENO); if (err != 0) { *debug << "failed file_actions_adddup2: " << strerror(err) << "\n"; goto done; } // spawn the child pid_t pid; err = posix_spawnp(&pid, argv[0], &fa, nullptr, argv.data(), get_environ()); if (err != 0) { *debug << "failed posix_spawnp: " << strerror(err) << "\n"; goto done; } // close the ends of the pipes we (the parent) don't need (void)close(in[READ_FD]); in[READ_FD] = -1; (void)close(out[WRITE_FD]); out[WRITE_FD] = -1; // now we're ready to sit in an event loop interacting with the child for (;;) { int nfds; // create a set of the out pipe and SIGCHLD to monitor for reading fd_set readfds; FD_ZERO(&readfds); FD_SET(out[READ_FD], &readfds); FD_SET(sigchld_pipe[READ_FD], &readfds); nfds = max(out[READ_FD], sigchld_pipe[READ_FD]); // create a set of only the in pipe (if still open) to monitor for writing fd_set writefds; FD_ZERO(&writefds); if (in[WRITE_FD] != -1) { FD_SET(in[WRITE_FD], &writefds); nfds = max(nfds, in[WRITE_FD]); } // wait for an event if (select(nfds, &readfds, &writefds, nullptr, nullptr) < 0) { // if our select call is correct, and any "error" should be an interrupt assert(errno == EAGAIN || errno == EINTR); } // clear any SIGCHLD notification if (FD_ISSET(sigchld_pipe[READ_FD], &readfds)) { char ignored[BUFSIZ]; ssize_t r __attribute__((unused)) = read(sigchld_pipe[READ_FD], ignored, sizeof(ignored)); } // read any data available from the child if (FD_ISSET(out[READ_FD], &readfds)) { ssize_t r; do { char buffer[BUFSIZ]; do { r = read(out[READ_FD], buffer, sizeof(buffer)); } while (r == -1 && errno == EINTR); if (r == -1) { if (errno == EAGAIN) break; *debug << "failed to read from child: " << strerror(errno) << "\n"; goto done; } // retain anything we read for (size_t i = 0; i < (size_t)r; i++) buffered_output << buffer[i]; } while (r > 0); } // write remaining data if the input pipe is ready if (in[WRITE_FD] != -1 && FD_ISSET(in[WRITE_FD], &writefds)) { if ((size_t)input_offset < input.size()) { ssize_t w; do { w = write(in[WRITE_FD], input.c_str() + input_offset, input.size() - input_offset); } while (w == -1 && errno == EINTR); if (w == -1 && errno != EAGAIN) { *debug << "failed to write to child: " << strerror(errno) << "\n"; goto done; } if (w > 0) { input_offset += (off_t)w; if ((size_t)input_offset == input.size()) { // exhausted the input; send the child EOF (void)close(in[WRITE_FD]); in[WRITE_FD] = -1; } } } } // check if the child has exited int status; if (waitpid(pid, &status, WNOHANG) == pid) { #if 0 /* It's useful to uncomment this when testing new SMT modes or new Rumur * syntax, to make a solver failure crash Rumur. Obviously you don't want * this enabled in a production build because the solver is just an * arbitrary user-defined program that's free to fail or crash if it wants * to. */ assert(WIFSTOPPED(status) || (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS)); #endif if (WIFEXITED(status)) { if (WEXITSTATUS(status) != EXIT_SUCCESS) *debug << "child returned exit status " << WEXITSTATUS(status) << "\n"; break; } if (WIFSIGNALED(status)) { *debug << "child exited due to signal " << WTERMSIG(status) << "\n"; break; } } } // success if we've reached here output = buffered_output.str(); ret = 0; done: for (int &fd : out) { if (fd != -1) { (void)close(fd); fd = -1; } } for (int &fd : in) { if (fd != -1) { (void)close(fd); fd = -1; } } (void)posix_spawn_file_actions_destroy(&fa); return ret; } static int __attribute__((unused)) test_process(int argc, char **argv) { if (argc < 2 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-?") == 0) { std::cerr << "Rumur Process.cc tester\n" << "\n" << " usage: " << argv[0] << " cmd args...\n"; return EXIT_SUCCESS; } // construct argument list std::vector args; for (size_t i = 1; i < (size_t)argc; i++) { assert(argv[i] != nullptr); args.emplace_back(argv[i]); } // read stdin until EOF std::ostringstream input; for (std::string line; std::getline(std::cin, line); ) { input << line << "\n"; } // run our designated process std::string output; int r = run(args, input.str(), output); if (r == 0) std::cout << output; return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } #ifdef TEST_PROCESS int main(int argc, char **argv) { return test_process(argc, argv); } #endif rumur-2020.02.17/rumur/src/process.h000066400000000000000000000006131362265074000170540ustar00rootroot00000000000000#pragma once #include #include #include /* Run an external process, pass it the given input on stdin and wait for it to * finish. Its stdout data is reported via the output parameter. Note that there * is currently no reporting of the exit status of the process. */ int run(const std::vector &args, const std::string &input, std::string &output); rumur-2020.02.17/rumur/src/resources.h000066400000000000000000000004611362265074000174110ustar00rootroot00000000000000#pragma once #include extern const unsigned char resources_includes_c[]; extern const size_t resources_includes_c_len; extern const unsigned char resources_header_c[]; extern const size_t resources_header_c_len; extern const unsigned char doc_rumur_1[]; extern const size_t doc_rumur_1_len; rumur-2020.02.17/rumur/src/rumur-run000066400000000000000000000123301362265074000171230ustar00rootroot00000000000000#!/usr/bin/env python3 ''' Wrapper script for running the Rumur model checker. This script is intended to be installed alongside the `rumur` binary from the Rumur model checker. It can then be used to quickly generate and run a model, as an alternative to having to run the model generation, compilation and execution steps manually. ''' import atexit import os import platform import shutil import subprocess as sp import sys import tempfile from typing import Optional def which(cmd: str) -> Optional[str]: ''' Equivalent of shell `which` ''' try: return sp.check_output(['which', cmd], stderr=sp.DEVNULL, universal_newlines=True).strip() except sp.CalledProcessError: return None # C compiler CC = which(os.environ.get('CC', 'cc')) def categorise(cc: str) -> str: ''' Determine the vendor of a given C compiler ''' # Create a temporary area to compile a test file tmp = tempfile.mkdtemp() # Setup the test file src = os.path.join(tmp, 'test.c') with open(src, 'wt') as f: f.write('#include \n' '#include \n' 'int main(void) {\n' '#ifdef __clang__\n' ' printf("clang\\n");\n' '#elif defined(__GNUC__)\n' ' printf("gcc\\n");\n' '#else\n' ' printf("unknown\\n");\n' '#endif\n' ' return EXIT_SUCCESS;\n' '}\n') categorisation = 'unknown' # Compile it aout = os.path.join(tmp, 'a.out') cc_proc = sp.run([cc, '-o', aout, src], universal_newlines=True, stdout=sp.DEVNULL, stderr=sp.DEVNULL) # Run it if cc_proc.returncode == 0: try: categorisation = sp.check_output([aout], universal_newlines=True).strip() except sp.CalledProcessError: pass # Clean up shutil.rmtree(tmp) return categorisation def supports(flag: str) -> bool: '''check whether the compiler supports a given command line flag''' # a trivial program to ask it to compile program = 'int main(void) { return 0; }' # compile it p = sp.run([CC, '-o', os.devnull, '-x', 'c', '-', flag], stderr=sp.DEVNULL, input=program.encode('utf-8', 'replace')) # check whether compilation succeeded return p.returncode == 0 def needs_libatomic() -> bool: '''check whether the compiler needs -latomic for a double-word compare-and-swap''' # CAS program to ask it to compile program = 'int main(void) {\n' \ '#if __SIZEOF_POINTER__ <= 4\n' \ ' uint64_t target = 0;\n' \ '#elif __SIZEOF_POINTER__ <= 8\n' \ ' unsigned __int128 target = 0;\n' \ '#endif\n' \ ' return __sync_val_compare_and_swap(&target, 0, 1);\n' \ '}\n' # compile it args = [CC, '-x', 'c', '-std=c11', '-', '-o', os.devnull] if supports('-mcx16'): args.append('-mcx16') p = sp.run(args, stderr=sp.DEVNULL, input=program.encode('utf-8', 'replace')) # check whether compilation succeeded return p.returncode != 0 def optimisation_flags() -> [str]: '''C compiler optimisation command line options for this platform''' flags = ['-O3'] # optimise code for the current host architecture if supports('-march=native'): flags.append('-march=native') # optimise code for the current host CPU if supports('-mtune=native'): flags.append('-mtune=native') # enable link-time optimisation if supports('-flto'): flags.append('-flto') cc_vendor = categorise(CC) # allow GCC to perform more advanced interprocedural optimisations if cc_vendor == 'gcc': flags.append('-fwhole-program') # on platforms that need it made explicit, enable CMPXCHG16B if supports('-mcx16'): flags.append('-mcx16') return flags def main(args: [str]) -> int: # Find the Rumur binary rumur_bin = which('rumur') if rumur_bin is None: rumur_bin = which(os.path.join(os.path.dirname(__file__), 'rumur')) if rumur_bin is None: sys.stderr.write('rumur binary not found\n') return -1 # if the user asked for help or version information, run Rumur directly for arg in args[1:]: if arg.startswith('-h') or arg.startswith('--h') or arg.startswith('--vers'): os.execv(rumur_bin, [rumur_bin] + args[1:]) if CC is None: sys.stderr.write('no C compiler found\n') return -1 # Generate the checker print('Generating the checker...') rumur_proc = sp.run([rumur_bin] + args[1:] + ['--output', '/dev/stdout'], stdin=sp.PIPE, stdout=sp.PIPE) if rumur_proc.returncode != 0: return rumur_proc.returncode checker_c = rumur_proc.stdout ok = True # Setup a temporary directory in which to generate the checker tmp = tempfile.mkdtemp() atexit.register(shutil.rmtree, tmp) # Compile the checker if ok: print('Compiling the checker...') aout = os.path.join(tmp, 'a.out') argv = [CC, '-std=c11'] + optimisation_flags() + ['-o', aout, '-x', 'c', '-', '-lpthread'] if needs_libatomic(): argv.append('-latomic') cc_proc = sp.run(argv, input=checker_c) ok &= cc_proc.returncode == 0 # Run the checker if ok: print('Running the checker...') checker_proc = sp.run([aout]) ok &= checker_proc.returncode == 0 return 0 if ok else -1 if __name__ == '__main__': try: sys.exit(main(sys.argv)) except KeyboardInterrupt: sys.exit(130) rumur-2020.02.17/rumur/src/smt/000077500000000000000000000000001362265074000160305ustar00rootroot00000000000000rumur-2020.02.17/rumur/src/smt/define-enum-members.cc000066400000000000000000000041061362265074000221640ustar00rootroot00000000000000#include #include #include "define-enum-members.h" #include #include "logic.h" #include "../options.h" #include #include "solver.h" #include #include "translate.h" #include using namespace rumur; namespace smt { namespace { class Definer : public ConstTypeTraversal { private: Solver *solver; public: explicit Definer(Solver &solver_): solver(&solver_) { } void visit_array(const Array &n) final { // define any enum members that occur in the index or element types dispatch(*n.index_type); dispatch(*n.element_type); } void visit_enum(const Enum &n) final { // ignore the built-in bool type that the solver already knows if (n.is_boolean()) return; // emit the members of the enum as integer constants mpz_class index = 0; size_t id = n.unique_id + 1; for (const std::pair &member : n.members) { assert(id < n.unique_id_limit && "unexpected number of enum members"); const std::string name = mangle(member.first, id); const std::string type = integer_type(); const std::string value = numeric_literal(index); *solver << "(declare-fun " << name << " () " << type << ")\n" << "(assert (= " << name << " " << value << "))\n"; index++; id++; } } void visit_range(const Range&) final { // as a primitive, ranges can't contain any enum members } void visit_record(const Record &n) final { // define any enum members that occur in the fields of this type for (const Ptr &field : n.fields) dispatch(*field->type); } void visit_typeexprid(const TypeExprID&) final { /* the referent of a TypeExprID will have occurred earlier in the AST and * already resulted in the relevant enum members being defined. */ } void visit_scalarset(const Scalarset&) final { // as a primitive, scalarsets can't contain any enum members } }; } void define_enum_members(Solver &solver, const TypeExpr &type) { Definer definer(solver); definer.dispatch(type); } } rumur-2020.02.17/rumur/src/smt/define-enum-members.h000066400000000000000000000004031362265074000220220ustar00rootroot00000000000000#pragma once #include #include #include "solver.h" namespace smt { /* declare any enum members that are syntactically contained under the given * type */ void define_enum_members(Solver &solver, const rumur::TypeExpr &type); } rumur-2020.02.17/rumur/src/smt/define-records.cc000066400000000000000000000032571362265074000212370ustar00rootroot00000000000000#include #include "define-records.h" #include #include "solver.h" #include #include "translate.h" #include "typeexpr-to-smt.h" using namespace rumur; namespace smt { namespace { class Definer : public ConstTypeTraversal { private: Solver *solver; public: explicit Definer(Solver &solver_): solver(&solver_) { } void visit_array(const Array &n) final { // the index has to be a simple type, so can't contain any records, but the // element might dispatch(*n.element_type); } void visit_enum(const Enum&) final { // nothing to do } void visit_range(const Range&) final { // nothing to do } void visit_record(const Record &n) final { // first, make sure we define any records that are children of this one for (const Ptr &field : n.fields) dispatch(*field->type); // now we're ready to define this one itself // synthesise a name for the SMT type const std::string name = mangle("", n.unique_id); // a name for the constructor (we never actually use this) const std::string ctor = "mk" + name; // declare the type *solver << "(declare-datatypes () ((" << name << " (" << ctor; for (const Ptr &field : n.fields) { const std::string fname = name + "_" + field->name; *solver << " (" << fname << " " << typeexpr_to_smt(*field->type) << ")"; } *solver << "))))\n"; } void visit_typeexprid(const TypeExprID&) final { // nothing to do } void visit_scalarset(const Scalarset&) final { // nothing to do } }; } void define_records(Solver &solver, const TypeExpr &type) { Definer definer(solver); definer.dispatch(type); } } rumur-2020.02.17/rumur/src/smt/define-records.h000066400000000000000000000002451362265074000210730ustar00rootroot00000000000000#pragma once #include #include #include "solver.h" namespace smt { void define_records(Solver &solver, const rumur::TypeExpr &type); } rumur-2020.02.17/rumur/src/smt/except.h000066400000000000000000000014501362265074000174710ustar00rootroot00000000000000// some exceptions the SMT components can throw #pragma once #include #include #include #include namespace smt { class BudgetExhausted : public std::runtime_error { public: BudgetExhausted(): std::runtime_error("SMT solver budget exhausted") { } }; class Unsupported : public std::runtime_error { public: const rumur::Expr *expr = nullptr; explicit Unsupported(): std::runtime_error("part of an expression was outside the " "currently implemented SMT functionality") { } explicit Unsupported(const rumur::Expr &expr_): std::runtime_error("SMT solver encountered unsupported expression \"" + expr_.to_string() + "\""), expr(&expr_) { } explicit Unsupported(const std::string &message): std::runtime_error(message) { } }; } rumur-2020.02.17/rumur/src/smt/logic.cc000066400000000000000000000033401362265074000174340ustar00rootroot00000000000000#include #include #include "logic.h" #include "../options.h" #include namespace smt { static const size_t BITVECTOR_WIDTH = 64; std::string integer_type() { if (options.smt.use_bitvectors) { return "(_ BitVec " + std::to_string(BITVECTOR_WIDTH) + ")"; } return "Int"; } std::string numeric_literal(const mpz_class &value) { if (value < 0) return "(" + neg() + " " + numeric_literal(-value) + ")"; if (options.smt.use_bitvectors) { return "(_ bv" + value.get_str() + " " + std::to_string(BITVECTOR_WIDTH) + ")"; } return value.get_str(); } std::string add() { if (options.smt.use_bitvectors) { return "bvadd"; } return "+"; } std::string div() { if (options.smt.use_bitvectors) { return "bvsdiv"; } // XXX: may cause solvers like CVC4 to fail with an error. Not visible to the // the user unless passing --debug though, so left as-is for now. return "div"; } std::string geq() { if (options.smt.use_bitvectors) { return "bvsge"; } return ">="; } std::string gt(void) { if (options.smt.use_bitvectors) { return "bvsgt"; } return ">"; } std::string leq() { if (options.smt.use_bitvectors) { return "bvsle"; } return "<="; } std::string lt() { if (options.smt.use_bitvectors) { return "bvslt"; } return "<"; } std::string mod() { if (options.smt.use_bitvectors) { return "bvsmod"; } return "mod"; } std::string mul() { if (options.smt.use_bitvectors) { return "bvmul"; } return "*"; } std::string neg() { if (options.smt.use_bitvectors) { return "bvneg"; } return "-"; } std::string sub() { if (options.smt.use_bitvectors) { return "bvsub"; } return "-"; } } rumur-2020.02.17/rumur/src/smt/logic.h000066400000000000000000000005541362265074000173020ustar00rootroot00000000000000#pragma once #include #include #include namespace smt { std::string integer_type(); std::string numeric_literal(const mpz_class &value); std::string add(); std::string div(); std::string geq(); std::string gt (); std::string leq(); std::string lt (); std::string mod(); std::string mul(); std::string neg(); std::string sub(); } rumur-2020.02.17/rumur/src/smt/simplify.cc000066400000000000000000000372241362265074000202030ustar00rootroot00000000000000#include #include #include "define-enum-members.h" #include "define-records.h" #include "except.h" #include "../log.h" #include "logic.h" #include "../options.h" #include #include "simplify.h" #include "solver.h" #include #include "translate.h" #include "typeexpr-to-smt.h" #include "../utils.h" using namespace rumur; namespace smt { /* Simplification logic. We only attempt to replace tautologies with true and * contradictions with false, rather than going further and removing unreachable * code. We assume the C compiler building the generated verifier is clever * enough to make these transformations itself, so we leave them for it. */ namespace { class Simplifier : public BaseTraversal { private: Solver *solver; public: explicit Simplifier(Solver &solver_): solver(&solver_) { } /* if you are editing the visitation logic, note that the calls to * open_scope/close_scope are intended to match the pattern in * ../../../librumur/src/resolve-symbols.cc so that the SMT solver resolves * ExprIDs/TypeExprIDs the same way we do */ void visit_add(Add &n) final { visit_bexpr(n); } void visit_aliasdecl(AliasDecl &n) final { dispatch(*n.value); simplify(n.value); } void visit_aliasrule(AliasRule &n) final { solver->open_scope(); for (Ptr &alias : n.aliases) { dispatch(*alias); declare_decl(*alias); } for (Ptr &rule : n.rules) dispatch(*rule); solver->close_scope(); } void visit_aliasstmt(AliasStmt &n) final { solver->open_scope(); for (Ptr &alias : n.aliases) { dispatch(*alias); declare_decl(*alias); } for (Ptr &stmt : n.body) dispatch(*stmt); solver->close_scope(); } void visit_and(And &n) final { visit_bexpr(n); } void visit_array(Array &n) final { dispatch(*n.index_type); dispatch(*n.element_type); } void visit_assignment(Assignment &n) final { dispatch(*n.lhs); dispatch(*n.rhs); simplify(n.rhs); } void visit_clear(Clear &n) final { dispatch(*n.rhs); simplify(n.rhs); } void visit_constdecl(ConstDecl &n) final { dispatch(*n.value); simplify(n.value); if (n.type != nullptr) dispatch(*n.type); } void visit_div(Div &n) final { visit_bexpr(n); } void visit_element(Element &n) final { dispatch(*n.array); dispatch(*n.index); simplify(n.array); simplify(n.index); } void visit_enum(Enum&) final { // nothing required (see declare_decl) } void visit_eq(Eq &n) final { visit_bexpr(n); } void visit_errorstmt(ErrorStmt&) final { } void visit_exists(Exists &n) final { solver->open_scope(); dispatch(n.quantifier); dispatch(*n.expr); simplify(n.expr); solver->close_scope(); } void visit_exprid(ExprID&) final { } void visit_field(Field &n) final { dispatch(*n.record); simplify(n.record); } void visit_for(For &n) final { solver->open_scope(); dispatch(n.quantifier); for (Ptr &stmt : n.body) dispatch(*stmt); solver->close_scope(); } void visit_forall(Forall &n) final { solver->open_scope(); dispatch(n.quantifier); dispatch(*n.expr); simplify(n.expr); solver->close_scope(); } void visit_function(Function &n) final { solver->open_scope(); for (Ptr ¶meter : n.parameters) { dispatch(*parameter); declare_decl(*parameter); } if (n.return_type != nullptr) dispatch(*n.return_type); for (Ptr &decl : n.decls) { dispatch(*decl); declare_decl(*decl); } for (Ptr &stmt : n.body) dispatch(*stmt); solver->close_scope(); } void visit_functioncall(FunctionCall &n) final { for (Ptr &arg : n.arguments) { dispatch(*arg); simplify(arg); } } void visit_geq(Geq &n) final { visit_bexpr(n); } void visit_gt(Gt &n) final { visit_bexpr(n); } void visit_if(If &n) final { for (IfClause &c : n.clauses) dispatch(c); } void visit_ifclause(IfClause &n) final { if (n.condition != nullptr) dispatch(*n.condition); for (Ptr &s : n.body) dispatch(*s); if (n.condition != nullptr) simplify(n.condition); } void visit_implication(Implication &n) final { visit_bexpr(n); } void visit_isundefined(IsUndefined &n) final { dispatch(*n.expr); simplify(n.expr); } void visit_leq(Leq &n) final { visit_bexpr(n); } void visit_lt(Lt &n) final { visit_bexpr(n); } void visit_mod(Mod &n) final { visit_bexpr(n); } void visit_model(Model &n) final { solver->open_scope(); for (Ptr &decl : n.decls) { dispatch(*decl); declare_decl(*decl); } for (Ptr &function : n.functions) { dispatch(*function); declare_func(*function); } for (Ptr &rule : n.rules) dispatch(*rule); solver->open_scope(); } void visit_mul(Mul &n) final { visit_bexpr(n); } void visit_negative(Negative &n) final { visit_uexpr(n); } void visit_neq(Neq &n) final { visit_bexpr(n); } void visit_not(Not &n) final { visit_uexpr(n); } void visit_number(Number&) final { } void visit_or(Or &n) final { visit_bexpr(n); } void visit_procedurecall(ProcedureCall &n) final { dispatch(n.call); } void visit_property(Property&) final { /* properties are printed to the user during verification, so don't simplify * the expression as it may confuse the user that it's not the same as what * they entered */ } void visit_propertyrule(PropertyRule &n) final { for (Quantifier &quantifier : n.quantifiers) dispatch(quantifier); for (Ptr &alias : n.aliases) dispatch(*alias); dispatch(n.property); } void visit_propertystmt(PropertyStmt &n) final { dispatch(n.property); } void visit_put(Put&) final { /* deliberately do nothing here because the expression in a 'put' statement * is displayed to the user during verification and will surprise them if it * has been simplified into something other than what they wrote */ } void visit_quantifier(Quantifier &n) final { if (n.type != nullptr) { dispatch(*n.type); declare_decl(*n.decl); } else { assert(n.from != nullptr); assert(n.to != nullptr); dispatch(*n.from); dispatch(*n.to); if (n.step != nullptr) dispatch(*n.step); simplify(n.from); simplify(n.to); if (n.step != nullptr) simplify(n.step); declare_decl(*n.decl); const std::string name = mangle(n.decl->name, n.decl->unique_id); if (n.from->constant()) { const std::string lb = numeric_literal(n.from->constant_fold()); *solver << "(assert (" << geq() << " " << name << " " << lb << "))\n"; } if (n.to->constant()) { const std::string ub = numeric_literal(n.to->constant_fold()); *solver << "(assert (" << leq() << " " << name << " " << ub << "))\n"; } } } void visit_range(Range &n) final { dispatch(*n.min); dispatch(*n.max); simplify(n.min); simplify(n.max); } void visit_record(Record &n) final { for (Ptr &field : n.fields) dispatch(*field); } void visit_return(Return &n) final { if (n.expr != nullptr) { dispatch(*n.expr); simplify(n.expr); } } void visit_ruleset(Ruleset &n) final { solver->open_scope(); for (Quantifier &quantifier : n.quantifiers) dispatch(quantifier); for (Ptr &alias : n.aliases) dispatch(*alias); for (Ptr &rule : n.rules) dispatch(*rule); solver->close_scope(); } void visit_scalarset(Scalarset &n) final { dispatch(*n.bound); simplify(n.bound); } void visit_simplerule(SimpleRule &n) final { solver->open_scope(); for (Quantifier &quantifier : n.quantifiers) dispatch(quantifier); for (Ptr &alias : n.aliases) dispatch(*alias); if (n.guard != nullptr) { dispatch(*n.guard); simplify(n.guard); } for (Ptr &decl : n.decls) { dispatch(*decl); declare_decl(*decl); } for (Ptr &stmt : n.body) dispatch(*stmt); solver->close_scope(); } void visit_startstate(StartState &n) final { solver->open_scope(); for (Quantifier &quantifier : n.quantifiers) dispatch(quantifier); for (Ptr &alias : n.aliases) dispatch(*alias); for (Ptr &decl : n.decls) { dispatch(*decl); declare_decl(*decl); } for (Ptr &stmt : n.body) dispatch(*stmt); solver->close_scope(); } void visit_sub(Sub &n) final { visit_bexpr(n); } void visit_switchcase(SwitchCase &n) final { for (Ptr &match : n.matches) { dispatch(*match); simplify(match); } for (Ptr &stmt : n.body) { dispatch(*stmt); } } void visit_switch(Switch &n) final { dispatch(*n.expr); simplify(n.expr); for (SwitchCase &c : n.cases) dispatch(c); } void visit_ternary(Ternary &n) final { dispatch(*n.cond); dispatch(*n.lhs); dispatch(*n.rhs); simplify(n.cond); simplify(n.lhs); simplify(n.rhs); } void visit_typedecl(TypeDecl &n) final { dispatch(*n.value); } void visit_typeexprid(TypeExprID&) final { } void visit_undefine(Undefine &n) final { dispatch(*n.rhs); } void visit_vardecl(VarDecl &n) final { dispatch(*n.type); } void visit_while(While &n) final { dispatch(*n.condition); simplify(n.condition); for (Ptr &stmt : n.body) dispatch(*stmt); } private: void visit_bexpr(BinaryExpr &n) { dispatch(*n.lhs); dispatch(*n.rhs); simplify(n.lhs); simplify(n.rhs); } void visit_uexpr(UnaryExpr &n) { dispatch(*n.rhs); simplify(n.rhs); } // try to squash a boolean expression to "True" or "False" void simplify(Ptr &e) { assert (e != nullptr && "attempt to simplify a NULL expression"); // we currently only handle boolean expressions if (!e->is_boolean()) return; // there's no point trying to simplify a literal we will replace with itself if (e->is_literal_true() || e->is_literal_false()) return; std::string claim; try { claim = translate(*e); } catch (Unsupported&) { *info << "skipping SMT simplification of unsupported expression \"" << e->to_string() << "\"\n"; return; } if (solver->is_true(claim)) { *info << "simplifying \"" << e->to_string() << "\" to true\n"; e = make_true(); } else if (solver->is_false(claim)) { *info << "simplifying \"" << e->to_string() << "\" to false\n"; e = make_false(); } } // invent a reference to "true" static Ptr make_true(void) { return Ptr(True); } // invent a reference to "false" static Ptr make_false(void) { return Ptr(False); } // declare a variable/type to the solver void declare_decl(const Decl &decl) { if (auto v = dynamic_cast(&decl)) { declare_var(v->name, v->unique_id, *v->type); return; } if (auto c = dynamic_cast(&decl)) { if (c->type == nullptr) { // integer constant assert(c->value->constant() && "non-constant value declared as constant"); const std::string value = numeric_literal(c->value->constant_fold()); const std::string name = mangle(c->name, c->unique_id); *solver << "(declare-fun " << name << " () " << integer_type() << ")\n" << "(assert (= " << name << " " << value << "))\n"; return; } // TODO: enum constants } if (auto t = dynamic_cast(&decl)) { // define any enum members that occur as part of this TypeDecl define_enum_members(*solver, *t->value); // define any records that occur as part of this TypeDecl define_records(*solver, *t->value); const std::string my_name = mangle(t->name, t->unique_id); // nested TypeDecl (i.e. a typedecl of a typedecl) if (auto ref = dynamic_cast(t->value.get())) { const std::string ref_name = mangle(ref->name, ref->referent->unique_id); *solver << "(define-sort " << my_name << " () " << ref_name << ")\n"; } else { // generic type definition *solver << "(define-sort " << my_name << " () " << typeexpr_to_smt(*t->value) << ")\n"; } return; } // TODO throw Unsupported(); } void declare_var(const std::string &name, size_t id, const TypeExpr &type) { // define any enum members that occur as part of the variable's type define_enum_members(*solver, type); // define any records that occur as part of this variable's type define_records(*solver, type); const std::string mangled = mangle(name, id); if (auto t = dynamic_cast(&type)) { // this has a previously defined type, so we know how to declare it const std::string tname = mangle(t->name, t->referent->unique_id); *solver << "(declare-fun " << mangled << " () " << tname << ")\n"; } else { // otherwise declare it generically const std::string tname = typeexpr_to_smt(type); *solver << "(declare-fun " << mangled << " () " << tname << ")\n"; } const Ptr t = type.resolve(); // the solver already knows boolean, so we're done if (t->is_boolean()) return; class ConstraintEmitter : public ConstTypeTraversal { private: Solver *solver; const std::string name; public: ConstraintEmitter(Solver &solver_, const std::string &name_): solver(&solver_), name(name_) { } void visit_array(const Array&) final { // no constraints required } void visit_enum(const Enum &n) final { // constrain its values based on the number of enum members const std::string zero = numeric_literal(0); *solver << "(assert (" << geq() << " " << name << " " << zero << "))\n"; const std::string size = numeric_literal(n.members.size()); *solver << "(assert (" << lt() << " " << name << " " << size << "))\n"; } void visit_range(const Range &n) final { // if this range's bounds are static, make them known to the solver if (n.constant()) { const std::string lb = numeric_literal(n.min->constant_fold()); const std::string ub = numeric_literal(n.max->constant_fold()); *solver << "(assert (" << geq() << " " << name << " " << lb << "))\n" << "(assert (" << leq() << " " << name << " " << ub << "))\n"; } } void visit_record(const Record&) final { // no constraints required } void visit_scalarset(const Scalarset &n) final { // scalarset values are at least 0 const std::string zero = numeric_literal(0); *solver << "(assert (" << geq() << " " << name << " " << zero << "))\n"; // if this scalarset's bounds are static, make them known to the solver if (n.constant()) { const std::string b = numeric_literal(n.bound->constant_fold()); *solver << "(assert (" << lt() << " " << name << " " << b << "))\n"; } } void visit_typeexprid(const TypeExprID&) final { assert(!"unreachable"); } }; ConstraintEmitter emitter(*solver, mangled); emitter.dispatch(*t); } void declare_func(const Function&) { throw Unsupported(); } }; } void simplify(Model &m) { // establish our connection to the solver Solver solver; // recursively traverse the model, simplifying as we go Simplifier simplifier(solver); simplifier.dispatch(m); } } rumur-2020.02.17/rumur/src/smt/simplify.h000066400000000000000000000003301362265074000200310ustar00rootroot00000000000000#pragma once #include namespace smt { /* Attempt to simplify expressions within a model using an external * Satisfiability Modulo Theories (SMT) solver. */ void simplify(rumur::Model &model); } rumur-2020.02.17/rumur/src/smt/solver.cc000066400000000000000000000062241362265074000176550ustar00rootroot00000000000000#include #include #include #include #include #include "../log.h" #include #include "../options.h" #include "../process.h" #include "except.h" #include "solver.h" #include #include namespace smt { // some helper functions to contain the visually noisy chrono API typedef std::chrono::time_point timestamp_t; static timestamp_t get_timestamp(void) { return std::chrono::system_clock::now(); } static mpz_class get_duration(const timestamp_t &start, const timestamp_t &end) { std::chrono::duration duration = end - start; return duration.count() * 1000; } Solver::Result Solver::solve(const std::string &claim, bool expectation) { if (time_used >= options.smt.budget) throw BudgetExhausted(); std::ostringstream query; // disable printing of "success" in response to commands query << "(set-option :print-success false)\n"; // set SMT logic if (options.smt.logic != "") query << "(set-logic " << options.smt.logic << ")\n"; // write any prelude the user requested for (const std::string &text : options.smt.prelude) { query << text << "\n"; } // append the declarations etc for (const std::shared_ptr &scope : prelude) query << scope->str(); // set up the main claim query << "(assert " << (expectation ? "(not " : "") << claim << (expectation ? ")" : "") << ")\n" << "(check-sat)\n"; *debug << "checking SMT problem:\n" << query.str(); // construct the call to the solver std::vector args; assert(options.smt.path != "" && "calling SMT solver without having supplied " "a path to it"); args.push_back(options.smt.path); std::copy(options.smt.args.begin(), options.smt.args.end(), std::back_inserter(args)); auto start = get_timestamp(); std::string output; int r = run(args, query.str(), output); auto end = get_timestamp(); time_used += get_duration(start, end); if (r < 0) { *debug << "SMT solver error\n"; return INCONCLUSIVE; } *debug << "SMT solver said:\n" << output; // look for a "sat" or "unsat" line std::istringstream ss(output); for (std::string line; std::getline(ss, line); ) { if (line == "sat") return SAT; if (line == "unsat") return UNSAT; } *debug << "inconclusive result from SMT solver\n"; return INCONCLUSIVE; } bool Solver::is_true(const std::string &claim) { return solve(claim, true) == UNSAT; } bool Solver::is_false(const std::string &claim) { return solve(claim, false) == UNSAT; } Solver &Solver::operator<<(const std::string &s) { assert(prelude.size() > 0 && "writing SMT content without an open scope"); *prelude[prelude.size() - 1] << s; return *this; } /* you might think we could just use "(push)" and "(pop)" for scoping, but some * SMT solvers only support these in incremental mode and we're calling the * solver in one-shot mode */ void Solver::open_scope(void) { prelude.push_back(std::make_shared()); } void Solver::close_scope(void) { assert(prelude.size() > 0 && "closing a scope when none are open"); prelude.pop_back(); } } rumur-2020.02.17/rumur/src/smt/solver.h000066400000000000000000000024411362265074000175140ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace smt { class Solver { private: std::vector> prelude; mpz_class time_used = 0; enum Result { SAT, UNSAT, INCONCLUSIVE }; /* Using the accrued prelude setup declarations, try to prove that the claim * expression is the expectation. I.e. prove the claim true or false depending * on whether expectation is true or false. For those unfamiliar with SMT * solvers, the way to interpret the result is: * SAT - there is a value(s) for which claim != expectation (proof failed) * UNSAT - for all values claim == expectation (proof succeeded) * INCONCLUSIVE - resource exhaustion, e.g. timeout */ Result solve(const std::string &claim, bool expectation); public: // can this expression be proven always-true? bool is_true(const std::string &claim); // can this expression be proven always-false? bool is_false(const std::string &claim); // add something to the prelude (e.g. a declaration "(declare-fun v () Int)") Solver &operator<<(const std::string &s); // open a new (nested) variable scope void open_scope(void); // close a scope void close_scope(void); }; } rumur-2020.02.17/rumur/src/smt/translate.cc000066400000000000000000000174011362265074000203370ustar00rootroot00000000000000#include #include #include "except.h" #include #include "logic.h" #include "../options.h" #include #include #include "translate.h" #include "../utils.h" using namespace rumur; namespace smt { namespace { class Translator : public ConstExprTraversal { private: std::ostringstream buffer; public: std::string str() const { return buffer.str(); } Translator &operator<<(const std::string &s) { buffer << s; return *this; } Translator &operator<<(const Expr &e) { dispatch(e); return *this; } void visit_add(const Add &n) { *this << "(" << add() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_and(const And &n) { *this << "(and " << *n.lhs << " " << *n.rhs << ")"; } void visit_element(const Element &n) { *this << "(select " << *n.array << " " << *n.index << ")"; } void visit_exprid(const ExprID &n) { *this << mangle(n.id, n.value->unique_id); } void visit_eq(const Eq &n) { *this << "(= " << *n.lhs << " " << *n.rhs << ")"; } void visit_exists(const Exists &n) { translate_quantified(n.quantifier, *n.expr, false); } void visit_div(const Div &n) { *this << "(" << div() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_field(const Field &n) { // the record type that forms the root of this expression will have // previously been defined as a synthesised type (see define-records.cc) // determine the (mangled) SMT name the root was given const Ptr type = n.record->type()->resolve(); const std::string root = mangle("", type->unique_id); // we can now compute the accessor for this field (see define-records.cc) const std::string getter = root + "_" + n.field; // now construct the SMT expression *this << "(" << getter << " " << *n.record << ")"; } void visit_forall(const Forall &n) { translate_quantified(n.quantifier, *n.expr, true); } void visit_functioncall(const FunctionCall &n) { throw Unsupported(n); } void visit_geq(const Geq &n) { *this << "(" << geq() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_gt(const Gt &n) { *this << "(" << gt() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_implication(const Implication &n) { *this << "(=> " << *n.lhs << " " << *n.rhs << ")"; } void visit_isundefined(const IsUndefined &n) { throw Unsupported(n); } void visit_leq(const Leq &n) { *this << "(" << leq() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_lt(const Lt &n) { *this << "(" << lt() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_mod(const Mod &n) { *this << "(" << mod() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_mul(const Mul &n) { *this << "(" << mul() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_negative(const Negative &n) { *this << "(" << neg() << " " << *n.rhs << ")"; } void visit_neq(const Neq &n) { *this << "(not (= " << *n.lhs << " " << *n.rhs << "))"; } void visit_number(const Number &n) { *this << numeric_literal(n.value); } void visit_not(const Not &n) { *this << "(not " << *n.rhs << ")"; } void visit_or(const Or &n) { *this << "(or " << *n.lhs << " " << *n.rhs << ")"; } void visit_sub(const Sub &n) { *this << "(" << sub() << " " << *n.lhs << " " << *n.rhs << ")"; } void visit_ternary(const Ternary &n) { *this << "(ite " << *n.cond << " " << *n.lhs << " " << *n.rhs << ")"; } private: void translate_quantified(const Quantifier &q, const Expr &e, bool forall) { // find a name for the quantified variable const std::string qname = mangle(q.decl->name, q.decl->unique_id); const std::string qtype = integer_type(); // determine the parts of the expression we will construct that depend on // forall const std::string binder = forall ? "forall" : "exists"; const std::string op = forall ? "or" : "and"; const std::string lb_rel = forall ? lt() : geq(); const std::string ub_rel1 = forall ? gt() : leq(); const std::string ub_rel2 = forall ? geq() : lt(); const std::string step_o = forall ? "(not " : ""; const std::string step_c = forall ? ")" : ""; // “∀q.”/“∃q.” *this << "(" << binder << " ((" << qname << " " << qtype << ")) (" << op; // “q < lb”/“q ≥ lb” *this << " (" << lb_rel << " " << qname << " "; if (q.type != nullptr) { const Ptr t = q.type->resolve(); if (isa(t) || isa(t)) { *this << numeric_literal(0); } else { assert(isa(t) && "non-(range|enum|scalarset) variable in " "quantifier"); const Range &r = dynamic_cast(*t); assert(r.min != nullptr && "unbounded range type"); *this << *r.min; } } else { assert(q.from != nullptr && "quantified variable has no type and also no " "lower bound"); *this << *q.from; } *this << ")"; // or/and “q > ub”/“q ≤ ub” *this << " ("; if (q.type != nullptr) { const Ptr t = q.type->resolve(); assert ((isa(t) || isa(t) || isa(t)) && "non-(range|enum|scalarset) variable in quantifier"); if (auto n = dynamic_cast(t.get())) { size_t s = n->members.size(); *this << ub_rel2 << " " << qname << " " << numeric_literal(s); } else if (auto r = dynamic_cast(t.get())) { assert (r->max != nullptr && "unbounded range type"); *this << ub_rel1 << " " << qname << " " << *r->max; } else { auto s = dynamic_cast(*t); *this << ub_rel2 << " " << qname << " " << *s.bound; } } else { assert(q.to != nullptr && "quantified variable has no type and also no " "upper bound"); *this << ub_rel1 << " " << qname << " " << *q.to; } *this << ")"; // or/and “!∃i. q = lb + i * step”/“∃i. q = lb + i * step” const std::string iname = qname + "_iteration"; *this << " " << step_o << "(exists ((" << iname << " " << qtype << ")) (= " << qname << " (" << add() << " "; if (q.type != nullptr) { const Ptr t = q.type->resolve(); if (isa(t) || isa(t)) { *this << numeric_literal(0); } else { assert(isa(t) && "non-(range|enum|scalarset) variable in " "quantifier"); const Range &r = dynamic_cast(*t); assert(r.min != nullptr && "unbounded range type"); *this << *r.min; } } else { *this << *q.from; } *this << " (" << mul() << " " << iname << " "; if (q.step == nullptr) { *this << numeric_literal(1); } else { *this << *q.step; } *this << "))))" << step_c; // finally the enclosed expression itself *this << " " << e << "))"; } }; } std::string translate(const Expr &expr) { Translator t; t.dispatch(expr); return t.str(); } static std::string lower(const std::string &s) { std::string s1; for (char c : s) s1 += tolower(c); return s1; } std::string mangle(const std::string &s, size_t id) { // if you're debugging a bad translation to SMT, you can change this to `true` // to get the Murphi name of a symbol output as a comment in the SMT problem const std::string prefix = false ? "; " + s + ":\n" : ""; const std::string l = lower(s); // if this is a boolean literal, the solver already knows of it if (l == "true" || l == "false") return prefix + l; // if this is the boolean type, the solver already knows of it if (l == "boolean") return prefix + "Bool"; // otherwise synthesise a node-unique name for this return prefix + "s" + std::to_string(id); } } rumur-2020.02.17/rumur/src/smt/translate.h000066400000000000000000000004621362265074000202000ustar00rootroot00000000000000#pragma once #include #include #include namespace smt { // translate an expression to its SMTLIB equivalent std::string translate(const rumur::Expr &expr); // name-mangle a symbol to make it a safe SMT variable std::string mangle(const std::string &s, size_t id); } rumur-2020.02.17/rumur/src/smt/typeexpr-to-smt.cc000066400000000000000000000037041362265074000214440ustar00rootroot00000000000000#include #include #include "except.h" #include "logic.h" #include "../options.h" #include #include #include #include "translate.h" #include "typeexpr-to-smt.h" using namespace rumur; namespace smt { namespace { class Translator : public ConstTypeTraversal { private: std::ostringstream out; public: Translator &operator<<(const std::string &s) { out << s; return *this; } Translator &operator<<(const TypeExpr &type) { dispatch(type); return *this; } std::string str() const { return out.str(); } void visit_array(const Array &n) final { *this << "(Array " << *n.index_type << " " << *n.element_type << ")"; } void visit_enum(const Enum &n) final { // the SMT solver already knows the type of booleans if (n.is_boolean()) { *this << "Bool"; return; } /* we assume our caller will emit the members of this enum if they are * relevant to them. */ *this << integer_type(); } void visit_range(const Range&) final { /* we assume our caller will eventually set the lower and upper bound * constraints for this integer if it is relevant to them */ *this << integer_type(); } void visit_record(const Record &n) final { // this type will have been previously constructed using its unique // identifier (see define-records.cc) *this << mangle("", n.unique_id); } void visit_scalarset(const Scalarset&) final { /* A scalarset is nothing special to the SMT solver. That is, we just * declare it as an integer and don't expect the solver to use any symmetry * reasoning. */ *this << integer_type(); } void visit_typeexprid(const TypeExprID &n) final { assert(n.referent != nullptr && "unresolved TypeExprID in AST"); *this << *n.referent->value; } }; } std::string typeexpr_to_smt(const TypeExpr &type) { Translator t; t.dispatch(type); return t.str(); } } rumur-2020.02.17/rumur/src/smt/typeexpr-to-smt.h000066400000000000000000000003401362265074000212770ustar00rootroot00000000000000#pragma once #include #include #include namespace smt { // translate a Rumur type into the equivalent SMTLIB type expression std::string typeexpr_to_smt(const rumur::TypeExpr &type); } rumur-2020.02.17/rumur/src/symmetry-reduction.cc000066400000000000000000000455341362265074000214320ustar00rootroot00000000000000#include #include #include #include #include #include "options.h" #include #include "symmetry-reduction.h" #include #include "utils.h" #include using namespace rumur; // Find all the named scalarset declarations in a model. static std::vector get_scalarsets(const Model &m) { std::vector ss; for (const Ptr &d : m.decls) { if (auto t = dynamic_cast(d.get())) { if (isa(t->value)) ss.push_back(t); } } return ss; } // Generate application of a swap of two state components static void generate_apply_swap(std::ostream &out, const std::string &offset_a, const std::string &offset_b, const TypeExpr &type, size_t depth = 0) { const Ptr t = type.resolve(); const std::string indent = std::string((depth + 1) * 2, ' '); if (t->is_simple()) { out << indent << "if (" << offset_a << " != " << offset_b << ") {\n" << indent << " raw_value_t a = handle_read_raw(s, state_handle(s, " << offset_a << ", " << t->width() << "ull));\n" << indent << " raw_value_t b = handle_read_raw(s, state_handle(s, " << offset_b << ", " << t->width() << "ull));\n" << indent << " handle_write_raw(s, state_handle(s, " << offset_b << ", " << t->width() << "ull), a);\n" << indent << " handle_write_raw(s, state_handle(s, " << offset_a << ", " << t->width() << "ull), b);\n" << indent << "}\n"; return; } if (auto a = dynamic_cast(t.get())) { const std::string var = "i" + std::to_string(depth); mpz_class ic = a->index_type->count() - 1; const std::string len = "((size_t)" + ic.get_str() + "ull)"; const std::string width = "((size_t)" + a->element_type->width().get_str() + "ull)"; out << indent << "for (size_t " << var << " = 0; " << var << " < " << len << "; " << var << "++) {\n"; const std::string off_a = offset_a + " + " + var + " * " + width; const std::string off_b = offset_b + " + " + var + " * " + width; generate_apply_swap(out, off_a, off_b, *a->element_type, depth + 1); out << indent << "}\n"; return; } if (auto r = dynamic_cast(t.get())) { std::string off_a = offset_a; std::string off_b = offset_b; for (const Ptr &f : r->fields) { generate_apply_swap(out, off_a, off_b, *f->type, depth); off_a += " + ((size_t)" + f->width().get_str() + "ull)"; off_b += " + ((size_t)" + f->width().get_str() + "ull)"; } return; } assert(!"missed case in generate_apply_swap"); } static void generate_swap_chunk(std::ostream &out, const TypeExpr &t, const std::string &offset, const TypeDecl &pivot, size_t depth = 0) { const std::string indent((depth + 1) * 2, ' '); if (t.is_simple()) { if (auto s = dynamic_cast(&t)) { if (s->name == pivot.name) { /* This state component has the same type as the pivot. If its value is * one of the pair we are swapping, we need to change it to the other. */ const std::string w = "((size_t)" + t.width().get_str() + "ull)"; const std::string h = "state_handle(s, " + offset + ", " + w + ")"; out << indent << "if (x != y) {\n" << indent << " raw_value_t v = handle_read_raw(s, " << h << ");\n" << indent << " if (v != 0) {\n" << indent << " if (v - 1 == (raw_value_t)x) {\n" << indent << " handle_write_raw(s, " << h << ", y + 1);\n" << indent << " } else if (v - 1 == (raw_value_t)y) {\n" << indent << " handle_write_raw(s, " << h << ", x + 1);\n" << indent << " }\n" << indent << " }\n" << indent << "}\n"; } } // A component of any other simple type is irrelevant. return; } const Ptr type = t.resolve(); if (auto a = dynamic_cast(type.get())) { const std::string w = "((size_t)" + a->element_type->width().get_str() + "ull)"; // If this array is indexed by our pivot type, swap the relevant elements auto s = dynamic_cast(a->index_type.get()); if (s != nullptr && s->name == pivot.name) { const std::string off_x = offset + " + x * " + w; const std::string off_y = offset + " + y * " + w; generate_apply_swap(out, off_x, off_y, *a->element_type, depth); } // Descend into its element to allow further swapping const std::string i = "i" + std::to_string(depth); mpz_class ic = a->index_type->count() - 1; const std::string len = "((size_t)" + ic.get_str() + "ull)"; out << indent << "for (size_t " << i << " = 0; " << i << " < " << len << "; " << i << "++) {\n"; const std::string off = offset + " + " + i + " * " + w; generate_swap_chunk(out, *a->element_type, off, pivot, depth + 1); out << indent << "}\n"; return; } if (auto r = dynamic_cast(type.get())) { std::string off = offset; for (const Ptr &f : r->fields) { generate_swap_chunk(out, *f->type, off, pivot, depth); off += " + ((size_t)" + f->width().get_str() + "ull)"; } return; } assert(!"missed case in generate_swap_chunk"); } static void generate_swap(const Model &m, std::ostream &out, const TypeDecl &pivot) { out << "static void swap_" << pivot.name << "(" << "struct state *s __attribute__((unused)), " << "size_t x __attribute__((unused)), " << "size_t y __attribute__((unused))) {\n"; for (const Ptr &d : m.decls) { if (auto v = dynamic_cast(d.get())) { std::string offset = "((size_t)" + v->offset.get_str() + "ull)"; generate_swap_chunk(out, *v->type, offset, pivot); } } out << "}\n\n"; } static void generate_loop_header(const TypeDecl &scalarset, size_t index, size_t level, std::ostream &out) { const std::string indent(level * 2, ' '); const Ptr type = scalarset.value->resolve(); auto s = dynamic_cast(type.get()); assert(s != nullptr); const std::string bound = "((size_t)" + s->bound->constant_fold().get_str() + "ull)"; const std::string i = "i" + std::to_string(index); out << indent << "if (state_cmp(&candidate, s) < 0) {\n" << indent << " /* Found a more canonical representation. */\n" << indent << " memcpy(s, &candidate, sizeof(*s));\n" << indent << "}\n\n" << indent << "{\n" << indent << " size_t schedule_" << scalarset.name << "[" << bound << "] = { 0 };\n\n" << indent << " for (size_t " << i << " = 0; " << i << " < " << bound << "; ) {\n" << indent << " if (schedule_" << scalarset.name << "[" << i << "] < " << i << ") {\n" << indent << " if (" << i << " % 2 == 0) {\n" << indent << " swap_" << scalarset.name << "(&candidate, 0, " << i << ");\n" << indent << " } else {\n" << indent << " swap_" << scalarset.name << "(&candidate, schedule_" << scalarset.name << "[" << i << "], " << i << ");\n" << indent << " }\n"; } static void generate_loop_footer(const TypeDecl &scalarset, size_t index, size_t level, std::ostream &out) { const std::string indent(level * 2, ' '); assert(isa(scalarset.value->resolve())); const std::string i = "i" + std::to_string(index); out << indent << " schedule_" << scalarset.name << "[" << i << "]++;\n" << indent << " " << i << " = 0;\n" << indent << " } else {\n" << indent << " schedule_" << scalarset.name << "[" << i << "] = 0;\n" << indent << " " << i << "++;\n" << indent << " }\n" << indent << " }\n" << indent << "}\n"; } static void generate_loop(const std::vector &scalarsets, size_t index, size_t level, std::ostream &out) { if (index < scalarsets.size() - 1) generate_loop(scalarsets, index + 1, level, out); generate_loop_header(*scalarsets[index], index, level, out); if (index < scalarsets.size() - 1) { generate_loop(scalarsets, index + 1, level + 3, out); } else { const std::string indent((level + 3) * 2, ' '); out << indent << "if (state_cmp(&candidate, s) < 0) {\n" << indent << " /* Found a more canonical representation. */\n" << indent << " memcpy(s, &candidate, sizeof(*s));\n" << indent << "}\n\n"; } generate_loop_footer(*scalarsets[index], index, level, out); } static void generate_canonicalise_exhaustive( const std::vector &scalarsets, std::ostream &out) { // Write the function prelude out << "static void state_canonicalise_exhaustive(struct state *s " "__attribute__((unused))) {\n" << "\n" << " assert(s != NULL && \"attempt to canonicalise NULL state\");\n" << "\n"; if (!scalarsets.empty()) { out << " /* A state to store the current permutation we are considering. */\n" << " static _Thread_local struct state candidate;\n" << " memcpy(&candidate, s, sizeof(candidate));\n" << "\n"; generate_loop(scalarsets, 0, 1, out); } // Write the function coda out << "}\n\n"; } static bool is_pivot(const TypeDecl &pivot, const TypeExpr *t) { if (t == nullptr) return false; auto s = dynamic_cast(t); if (s == nullptr) return false; return pivot.name == s->name; } // Generate application of a comparison of two state components static void generate_apply_compare(std::ostream &out, const TypeExpr &type, const std::string &offset_a, const std::string &offset_b, const TypeDecl &pivot, size_t depth = 0, bool used_pivot = false) { const Ptr t = type.resolve(); const std::string indent((depth + 1) * 2, ' '); if (t->is_simple()) { /* Emit a comparison of this state component, unless it's scoped by the same * scalarset as ourselves. If we're already doing a comparison based on this * scalarset, we don't want to reuse it because this value itself will * change if/when the parent (array) component is reshuffled. */ if (!used_pivot || !is_pivot(pivot, &type)) { out << indent << "if (" << offset_a << " != " << offset_b << ") {\n" << indent << " raw_value_t a = handle_read_raw(s, state_handle(s, " << offset_a << ", " << t->width() << "ull));\n" << indent << " raw_value_t b = handle_read_raw(s, state_handle(s, " << offset_b << ", " << t->width() << "ull));\n" << indent << " if (a < b) {\n" << indent << " return -1;\n" << indent << " } else if (a > b) {\n" << indent << " return 1;\n" << indent << " }\n" << indent << "}\n"; } return; } if (auto a = dynamic_cast(t.get())) { if (!used_pivot || !is_pivot(pivot, a->index_type.get())) { used_pivot |= is_pivot(pivot, a->index_type.get()); const std::string var = "i" + std::to_string(depth); mpz_class ic = a->index_type->count() - 1; const std::string len = "((size_t)" + ic.get_str() + "ull)"; const std::string width = "((size_t)" + a->element_type->width().get_str() + "ull)"; out << indent << "for (size_t " << var << " = 0; " << var << " < " << len << "; " << var << "++) {\n"; const std::string off_a = offset_a + " + " + var + " * " + width; const std::string off_b = offset_b + " + " + var + " * " + width; generate_apply_compare(out, *a->element_type, off_a, off_b, pivot, depth + 1, used_pivot); out << indent << "}\n"; } return; } if (auto r = dynamic_cast(t.get())) { std::string off_a = offset_a; std::string off_b = offset_b; for (const Ptr &f : r->fields) { generate_apply_compare(out, *f->type, off_a, off_b, pivot, depth, used_pivot); off_a += " + ((size_t)" + f->width().get_str() + "ull)"; off_b += " + ((size_t)" + f->width().get_str() + "ull)"; } return; } assert(!"missed case in generate_apply_compare"); } // Generate part of a memcmp-style comparator static void generate_compare_chunk(std::ostream &out, const TypeExpr &t, const std::string offset, const TypeDecl &pivot, size_t depth = 0, bool used_pivot = false) { const std::string indent((depth + 1) * 2, ' '); if (t.is_simple()) { /* If this state component has the same type as the pivot, we need to see if * it matches either of the operands. Here, we are basically looking to see * which (if either) of the scalarset elements appears *first* in the state. */ if (is_pivot(pivot, &t) && !used_pivot) { const std::string width = "((size_t)" + t.width().get_str() + "ull)"; out /* Open a scope so we don't need to think about redeclaring/shadowing * 'v'. */ << indent << "{\n" << indent << " raw_value_t v = handle_read_raw(s, state_handle(s, " << offset << ", " << width << "));\n" << indent << " if (v != 0) { /* ignored 'undefined' */\n" << indent << " if (v - 1 == (raw_value_t)x) {\n" << indent << " return -1;\n" << indent << " } else if (v - 1 == (raw_value_t)y) {\n" << indent << " return 1;\n" << indent << " }\n" << indent << " }\n" // Close scope << indent << "}\n"; } // Nothing required for any other simply type. return; } const Ptr type = t.resolve(); if (auto a = dynamic_cast(type.get())) { // The bit size of each array element as a C code string const std::string width = "((size_t)" + a->element_type->width().get_str() + "ull)"; /* If this array is indexed by the pivot type, first compare the relevant * elements. Note, we'll only end up descending if the two elements happen * to be equal. */ if (is_pivot(pivot, a->index_type.get()) && !used_pivot) { const std::string off_x = offset + " + x * " + width; const std::string off_y = offset + " + y * " + width; generate_apply_compare(out, *a->element_type, off_x, off_y, pivot, depth, true); used_pivot = true; } // Descend into its elements to allow further comparison // The number of elements in this array as a C code string mpz_class ic = a->index_type->count() - 1; const std::string ub = "((size_t)" + ic.get_str() + "ull)"; // Generate a loop to iterate over all the elements const std::string var = "i" + std::to_string(depth); out << indent << "for (size_t " << var << " = 0; " << var << " < " << ub << "; " << var << "++) {\n"; // Generate code to compare each element const std::string off = offset + " + " + var + " * " + width; generate_compare_chunk(out, *a->element_type, off, pivot, depth + 1, used_pivot); // Close the loop out << indent << "}\n"; return; } if (auto r = dynamic_cast(type.get())) { std::string off = offset; for (const Ptr &f : r->fields) { // Generate code to compare this field generate_compare_chunk(out, *f->type, off, pivot, depth, used_pivot); // Jump over this field to get the offset of the next field const std::string width = "((size_t)" + f->width().get_str() + "ull)"; off += " + " + width; } return; } assert(!"missed case in generate_compare_chunk"); } /* Generate a memcmp-style comparator for a given scalarset with respect to the * state. */ static void generate_compare(std::ostream &out, const TypeDecl &pivot, const Model &m) { out << "static int compare_" << pivot.name << "(const struct state *s, " << "size_t x, size_t y) {\n" << "\n" << " if (x == y) {\n" << " return 0;\n" << " }\n" << "\n"; for (const Ptr &d : m.decls) { if (auto v = dynamic_cast(d.get())) { const std::string offset = "((size_t)" + v->offset.get_str() + "ull)"; generate_compare_chunk(out, *v->type, offset, pivot); } } out // Fall through case where all components were equal << " return 0;\n" << "}\n"; } static void generate_sort(std::ostream &out, const TypeDecl &pivot) { assert(isa(pivot.value->resolve())); out << "static void sort_" << pivot.name << "(struct state *s, " << "size_t lower, size_t upper) {\n" << "\n" << " /* If we have nothing to sort, bail out. */\n" << " if (lower >= upper) {\n" << " return;\n" << " }\n" << "\n" << " /* Use Hoare's partitioning algorithm to apply quicksort. */\n" << " size_t pivot = lower;\n" // <- this is "pivot" in the quicksort sense << " size_t i = lower - 1;\n" << " size_t j = upper + 1;\n" << "\n" << " for (;;) {\n" << "\n" << " do {\n" << " i++;\n" << " assert(i >= lower && i <= upper && \"out of bounds access in " << "sort_" << pivot.name << "()\");\n" << " } while (compare_" << pivot.name << "(s, i, pivot) < 0);\n" << "\n" << " do {\n" << " j--;\n" << " assert(j >= lower && j <= upper && \"out of bounds access in " << "sort_" << pivot.name << "()\");\n" << " } while (compare_" << pivot.name << "(s, j, pivot) > 0);\n" << "\n" << " if (i >= j) {\n" << " break;\n" << " }\n" << "\n" << " /* Swap elements i and j. */\n" << " swap_" << pivot.name << "(s, i, j);\n" << " if (i == pivot) {\n" << " pivot = j;\n" << " } else if (j == pivot) {\n" << " pivot = i;\n" << " }\n" << " }\n" << "\n" << " sort_" << pivot.name << "(s, lower, j);\n" << " sort_" << pivot.name << "(s, j + 1, upper);\n" << "}\n"; } static void generate_canonicalise_heuristic(const Model &m, const std::vector &scalarsets, std::ostream &out) { for (const TypeDecl *t : scalarsets) { generate_compare(out, *t, m); generate_sort(out, *t); } out << "static void state_canonicalise_heuristic(struct state *s " "__attribute__((unused))) {\n" << "\n" << " assert(s != NULL && \"attempt to canonicalise NULL state\");\n" << "\n"; for (const TypeDecl *t : scalarsets) { const Ptr type = t->value->resolve(); auto s = dynamic_cast(type.get()); assert(s != nullptr); mpz_class bound = s->count() - 1; out << " sort_" << t->name << "(s, 0, ((size_t)" << bound.get_str() << "ull) - 1);\n"; } out << "}\n\n"; } void generate_canonicalise(const Model &m, std::ostream &out) { // Find types eligible for use in canonicalisation const std::vector scalarsets = get_scalarsets(m); // Generate functions to swap state elements with respect to each scalarset for (const TypeDecl *t : scalarsets) generate_swap(m, out, *t); generate_canonicalise_exhaustive(scalarsets, out); generate_canonicalise_heuristic(m, scalarsets, out); } rumur-2020.02.17/rumur/src/symmetry-reduction.h000066400000000000000000000005531362265074000212640ustar00rootroot00000000000000#pragma once #include #include #include /* Generate the `state_canonicalise` function for symmetry reduction. Rumur * generates this whether you have symmetry reduction enabled or not, but it * will only be used when symmetry reduction is enabled. */ void generate_canonicalise(const rumur::Model &m, std::ostream &out); rumur-2020.02.17/rumur/src/utils.cc000066400000000000000000000015501362265074000166750ustar00rootroot00000000000000#include #include #include "options.h" #include #include #include #include "utils.h" using namespace rumur; static std::string octal(char c) { char buffer[sizeof("\\000")]; snprintf(buffer, sizeof(buffer), "\\%03o", c); return buffer; } std::string escape(const std::string &s) { std::string out; for (const char &c : s) { if (iscntrl(c) || c == '\\' || c == '\"') { out += "\\" + octal(c); } else { out += c; } } return out; } std::string to_C_string(const Expr &expr) { return "\"" + escape(expr.to_string()) + "\""; } static std::string to_string(const location &location) { std::stringstream ss; ss << location; return ss.str(); } std::string to_C_string(const location &location) { return "\"" + escape(input_filename) + ":" + to_string(location) + ": \""; } rumur-2020.02.17/rumur/src/utils.h000066400000000000000000000010201362265074000165270ustar00rootroot00000000000000#pragma once #include #include #include template bool isa(const U ptr) { return ptr != nullptr && dynamic_cast(&*ptr) != nullptr; } // escape a string for the purposes of outputting it to a C source file std::string escape(const std::string &s); // get a C source code string for this expression std::string to_C_string(const rumur::Expr &expr); // get a C source code string for this location std::string to_C_string(const rumur::location &location); rumur-2020.02.17/tests/000077500000000000000000000000001362265074000144265ustar00rootroot00000000000000rumur-2020.02.17/tests/README.rst000066400000000000000000000013671362265074000161240ustar00rootroot00000000000000Integration Tests ================= This directory contains an integration test suite for Rumur. It is expected to be run with the current working directory as your build output directory: .. code-block:: sh cd /my/build/dir cmake /my/source/dir make /my/source/dir/tests/integration-tests.py Within this directory are various test cases, each defined in a .m file. It is possible to tweak the expected outcome of a test using specially formatted comments in the source of a test case. E.g. to indicate that Rumur is expected to reject a test case: .. code-block:: murphi -- rumur_exit_code: 1 To see how these comments are interpreted and gain a full understanding of what is possible, consult the test script integration-tests.py. rumur-2020.02.17/tests/alias-in-bound.m000066400000000000000000000005551362265074000174130ustar00rootroot00000000000000-- this model provokes a problem first observed on commit -- 538dd85228b98fce4e506883523c0afbf9647e0f wherein an alias of a constant would -- not be recognised as a constant as thus not be usable in a range const N: 2; var x: 0 .. N; startstate begin x := 0; end; alias y: N do ruleset i: 0 .. y do rule begin x := N - i; end; end; end; rumur-2020.02.17/tests/alias-in-bound2.m000066400000000000000000000004331362265074000174700ustar00rootroot00000000000000-- variant on alias-in-bound.m that uses the alias in a local range instead of a -- ruleset range const N: 2; var x: 0 .. N; startstate begin x := 0; end; rule begin alias y: N do for i: 0 .. y do x := N - i; end; end; end; rule begin x := N - x; end; rumur-2020.02.17/tests/apartment.m000066400000000000000000000100451362265074000165770ustar00rootroot00000000000000/* This model represents a situation I had where a friend was staying with me * and I had lent them one of my keys to get in. It's primarily used as a test * of the 'liveness' keyword. However, a close reading can probably also reveal * the secrets to robbing me. */ type location: enum { APARTMENT, -- inside the apartment ELEVATOR, -- the building elevator GARAGE, -- in the underground garage HALLWAY, -- the hallway on my floor LOBBY, -- the lobby of the building STAIRWELL, -- in the building stairwell STREET, -- in the street outside } person: enum { ME, FRIEND } var -- the location of myself and my friend people: array[person] of location startstate begin -- I start in my apartment people[ME] := APARTMENT; -- my friend is outside people[FRIEND] := STREET; end ruleset p: person do -- anyone can leave if they're in the apartment rule "exit apartment" people[p] = APARTMENT ==> begin people[p] := HALLWAY; end /* either of us can get into the apartment from the hallway because we both * have a key */ rule "enter apartment" people[p] = HALLWAY ==> begin people[p] := APARTMENT; end -- the elevator has no security, except in the garage rule "enter elevator" people[p] = HALLWAY | people[p] = LOBBY ==> begin people[p] := ELEVATOR; end rule "exit elevator to hallway" people[p] = ELEVATOR ==> begin people[p] := HALLWAY; end rule "exit elevator to lobby" people[p] = ELEVATOR ==> begin people[p] := LOBBY; end rule "exit elevator to garage" people[p] = ELEVATOR ==> begin people[p] := GARAGE; end -- the stairwell has no security to get in -- note: not possible to enter the stairwell from the street rule "enter stairwell" people[p] = HALLWAY | people[p] = LOBBY | people[p] = GARAGE ==> begin people[p] := STAIRWELL; end /* the stairwell only requires a key to get out on my level or the lobby, but * it's the same key both of us have a copy of */ rule "exit stairwell to street" people[p] = STAIRWELL ==> begin people[p] := STREET; end rule "exit stairwell to garage" people[p] = STAIRWELL ==> begin people[p] := GARAGE; end rule "exit stairwell to lobby" people[p] = STAIRWELL ==> begin people[p] := LOBBY; end rule "exit stairwell to hallway" people[p] = STAIRWELL ==> begin people[p] := HALLWAY; end -- you can exit the lobby to the street with no key rule "exit lobby to street" people[p] = LOBBY ==> begin people[p] := STREET; end -- anyone can exit the garage onto the street rule "exit garage to street" people[p] = GARAGE ==> begin people[p] := STREET; end end /* you can only enter the lobby from the street with the door fob, which my * friend has */ rule "enter lobby from street" people[FRIEND] = STREET ==> begin people[FRIEND] := LOBBY; end -- my friend can also let me in if I'm outside rule "enter lobby from street with me" people[FRIEND] = STREET & people[ME] = STREET ==> begin people[FRIEND] := LOBBY; people[ME] := LOBBY; end -- you can only enter the garage from the street with the remote, which I have rule "enter garage from street" people[ME] = STREET ==> begin people[ME] := GARAGE; end -- I can let my friend in if he's with me rule "enter garage from street with friend" people[ME] = STREET & people[FRIEND] = STREET ==> begin people[ME] := GARAGE; people[FRIEND] := GARAGE; end /* getting into the elevator from the garage for some reason requires the door * fob, which my friend has */ rule "enter elevator from garage" people[FRIEND] = GARAGE ==> begin people[FRIEND] := ELEVATOR; end -- if we're together, my friend can swipe me in rule "enter elevator from garage together" people[FRIEND] = GARAGE & people[ME] = GARAGE ==> begin people[FRIEND] := ELEVATOR; people[ME] := ELEVATOR; end -- regardless of where we are, I should be able to get back into my apartment liveness "I can get in" people[ME] = APARTMENT -- similarly, my friend should always be able to get in liveness "my friend can get in" people[FRIEND] = APARTMENT rumur-2020.02.17/tests/arithmetic-on-heterogeneous-ranges.m000066400000000000000000000003631362265074000235000ustar00rootroot00000000000000/* Tests that arithmetic between two values of differing range types is * supported. */ var x: 1..10; y: 1..5; startstate begin x := 1; y := 1; end; rule x > 1 ==> begin x := x - y; end; rule x < 10 ==> begin x := x + y; end; rumur-2020.02.17/tests/array-assignment.m000066400000000000000000000003251362265074000200700ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] type foo_t: array[0 .. 1] of boolean; var x: foo_t; startstate begin x[0] := true; x[1] := false; end; rule begin x[0] := x[1]; x[1] := x[0]; end; rumur-2020.02.17/tests/assert-syntax.m000066400000000000000000000003311362265074000174260ustar00rootroot00000000000000-- Test assertions with both orderings of its arguments var x: boolean; startstate begin x := true; end; rule begin assert x | !x "hello"; x := !x; end; rule begin assert "world" x | !x; x := !x; end; rumur-2020.02.17/tests/assertion-type-limits.m000066400000000000000000000006001362265074000210650ustar00rootroot00000000000000/* This model is designed to provoke a case where the size of the generated * state is 31 bits. On 529f40ddfff9de06c716b093dd229e754940ac9b it was observed * that this generates code in debug mode that generates a compiler warning when * built with -Wtype-limits. */ type foo_t: 0 .. 1073741824; var x: foo_t; startstate begin x := 0; end; rule begin x := 1 - x; end; rumur-2020.02.17/tests/assume-in-ruleset.m000066400000000000000000000003321362265074000201640ustar00rootroot00000000000000-- Test using an assumption inside a ruleset var x: 0 .. 10 startstate begin x := 0; end rule x > 0 ==> begin x := x - 1; end rule x < 4 ==> begin x := x + 1; end ruleset y: 5 .. 10 do assume x != y end rumur-2020.02.17/tests/assume-statement.m000066400000000000000000000002131362265074000200770ustar00rootroot00000000000000-- test using an assumption as a statement var x: 0 .. 2 startstate begin x := 0; end rule begin x := 1 - x; assume x != 2; end rumur-2020.02.17/tests/assume-statement2.m000066400000000000000000000006231362265074000201660ustar00rootroot00000000000000/* Test using an assumption that fails as a statement. This was observed to * cause a segfault or assertion failure on * 138877410d739f1d9fc07ee8f1107f8f3c5676de. */ var x: 0 .. 2 startstate begin x := 0; end rule x > 0 ==> begin x := x - 1; end rule x < 2 ==> begin x := x + 1; assume x != 2; end -- if the assumption is working correctly, this invariant should pass invariant x != 2 rumur-2020.02.17/tests/assume-statement3.m000066400000000000000000000005501362265074000201660ustar00rootroot00000000000000-- Similar to assume-statement2.m, but with the assumption inside a function. var x: 0 .. 2 procedure foo(a: 0 .. 2); begin assume a != 2; end; startstate begin x := 0; end rule x > 0 ==> begin x := x - 1; end rule x < 2 ==> begin x := x + 1; foo(x); end -- if the assumption is working correctly, this invariant should pass invariant x != 2 rumur-2020.02.17/tests/assume-statement4.m000066400000000000000000000006461362265074000201750ustar00rootroot00000000000000/* Similar to assume-statement2.m, but with the assumption inside a nested * function. */ var x: 0 .. 2 procedure foo(a: 0 .. 2); begin assume a != 2; end; procedure bar(b: 0 .. 2); begin foo(b); end; startstate begin x := 0; end rule x > 0 ==> begin x := x - 1; end rule x < 2 ==> begin x := x + 1; bar(x); end -- if the assumption is working correctly, this invariant should pass invariant x != 2 rumur-2020.02.17/tests/bad-alias.m000066400000000000000000000006051362265074000164220ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model should be rejected due to the incorrect usage of 'y' within the * alias block. However, on commit c65737161d9151d8a69ad718aea370636ff73829 it * was observed that this actually causes an assertion failure instead. This was * originally found by AFL. */ var x: boolean; startstate begin end; rule begin alias y: 1 do y := !y.x; end; end; rumur-2020.02.17/tests/bad-array-index.m000066400000000000000000000005751362265074000175620ustar00rootroot00000000000000-- rumur_exit_code: 1 /* The following model should fail code generation due to the invalid array * index. However, in commit c65737161d9151d8a69ad718aea370636ff73829 it was * observed that this actually causes an assertion failure. This issue was * originally found by AFL. */ type foo_t: array[0 .. 1] of boolean; var x: foo_t; startstate begin x[0][0] := x[1]; end; rumur-2020.02.17/tests/bad-enum-print.m000066400000000000000000000010761362265074000174320ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile('enum { HELLO, WORLD }') /* This model attempts to provoke a bug first observed on commit * 42b424c6d5986e44c218152e96b065b056ea494b. Enums would be incorrectly printed * without separating spaces. If this bug has been reintroduced, the following * model will fail with an assertion message that says something incorrect like * "enum { HELLOWORLD }". */ var x: boolean; startstate begin x := true; end; rule begin x := !x; assert forall y: enum { HELLO, WORLD } do false end; end; rumur-2020.02.17/tests/bad-expr-type-ref.m000066400000000000000000000003611362265074000200370ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This test contains an ID expression that incorrectly refers to a typedecl. * This should result in an error. */ type t: boolean; var x: boolean; startstate begin x := true; end; rule begin x := t; end; rumur-2020.02.17/tests/bad-field.m000066400000000000000000000006151362265074000164150ustar00rootroot00000000000000-- rumur_exit_code: 1 /* The following model should be rejected due to the use of 'x' as if it were a * record. However, on commit c65737161d9151d8a69ad718aea370636ff73829 it was * observed that this actually causes an uncaught exception instead. This was * originally found by AFL. */ type t: enum { A, B }; var x: t; startstate begin x := A; end; rule x.A ==> begin x := B; end; rumur-2020.02.17/tests/bad-function-call.m000066400000000000000000000006541362265074000200730ustar00rootroot00000000000000-- rumur_exit_code: 1 /* Rumur should reject the following model due to the incorrect call of function * foo. However, on commit c65737161d9151d8a69ad718aea370636ff73829 it was * observed that this actually caused an assertion failure. This was originally * found by AFL. */ var x: boolean; function foo(a: boolean): boolean; begin return a; end; startstate begin x := true; end; rule begin x := !foo(x, x); end; rumur-2020.02.17/tests/bad-function-parameter.m000066400000000000000000000010171362265074000211320ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model is designed to provoke a bug observed on commit * ad4078683bd8737add75a294fec65d8bd2f6b84b, wherein passing a boolean literal * in as a var parameter to a function caused an uncaught exception. Rumur * should correctly detect and handle this case, but if the bug has been * reintroduced an uncaught exception will occur during code generation. */ var x: boolean; procedure foo(var y: boolean); begin end; startstate begin x := true; end; rule begin foo(true); x := !x; end; rumur-2020.02.17/tests/bad-invariant-number.m000066400000000000000000000013061362265074000206110ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'\binvariant 1 failed\b') /* This test checks for the presence of a previous bug first observed in commit * e1b74738ba73df5352622464be47b69db688c633, wherein invariants are incorrectly * numbered. This problem occurs because all properties are counted together, so * non-invariant properties that precede an invariant cause it to get a higher * number than expected. If this problem has been reintroduced, the model will * call the invariant "invariant 3" in its error message. */ var x: boolean startstate begin x := true; end rule begin x := !x; end rule begin x := !x; end assume true assume true invariant x rumur-2020.02.17/tests/bad-lvalue.m000066400000000000000000000004151362265074000166200ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model deliberately uses something that shouldn't be assignable as the * LHS of an assignment, which should trigger an error. */ const N: 0; var x: boolean; startstate begin x := true; end; rule begin N := 0; x := !x; end; rumur-2020.02.17/tests/basic-aliasrule.m000066400000000000000000000001531362265074000176430ustar00rootroot00000000000000var x: boolean; startstate begin x := true; end; alias y: x do rule begin y := !y; end; end; rumur-2020.02.17/tests/basic-const.m000066400000000000000000000001721362265074000170110ustar00rootroot00000000000000const N: 2; var x: boolean; startstate begin x := true; end; rule begin if N = 2 then x := !x; end; end; rumur-2020.02.17/tests/basic-invariant.m000066400000000000000000000002221362265074000176520ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] var x: boolean; startstate begin x := true; end; rule begin x := x; end; invariant x; rumur-2020.02.17/tests/basic-ruleset.m000066400000000000000000000002111362265074000173400ustar00rootroot00000000000000type foo_t: 1 .. 10; var x: boolean; startstate begin x := true; end; ruleset y: foo_t do rule begin x := !x; end; end; rumur-2020.02.17/tests/basic-ruleset2.m000066400000000000000000000001621362265074000174270ustar00rootroot00000000000000var x: boolean; startstate begin x := true; end; ruleset y: boolean do rule begin x := y; end; end; rumur-2020.02.17/tests/basic-sandbox.m000066400000000000000000000006451362265074000173260ustar00rootroot00000000000000-- rumur_flags: ['--sandbox', 'on'] -- skip_reason: None if has_sandbox() else 'no suitable sandboxing facilities available on this platform' /* Test that the sandboxing functionality is usable. Note that this merely * checks that the sandboxing option results in compilable code, not that the * sandbox itself is actually secure. */ var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/bfs-vs-dfs.m000066400000000000000000000011321362265074000165530ustar00rootroot00000000000000-- checker_exit_code: 1 /* A model to explore Breadth-First Search vs Depth-First Search as exploration * strategies. This model contains multiple errors, different ones of which will * be found depending on which search strategy you use. */ var x: boolean; y: boolean; z: boolean; w: boolean; startstate begin x := false; y := false; z := false; w := false; end; rule "A" !x ==> begin x := true; end; rule "B" !x ==> begin y := true; end; rule "C" y ==> begin z := true; end; rule "D" z ==> begin w := true; end; rule "E" x ==> begin w := true; end; invariant !w rumur-2020.02.17/tests/boolean-array-index.m000066400000000000000000000004511362265074000204440ustar00rootroot00000000000000/* A model that uses an array with a boolean type indexing it. */ type bar_t: 0 .. 1; foo_t: array [boolean] of bar_t; var x: foo_t; startstate begin x[false] := 0; x[true] := 1; end; rule begin x[false] := 0; x[true] := 1; end; rule begin x[false] := 1; x[true] := 0; end; rumur-2020.02.17/tests/boolean-array.m000066400000000000000000000003251362265074000173370ustar00rootroot00000000000000type foo_t: array[0 .. 1] of boolean; var x: foo_t; startstate begin x[0] := true; x[1] := false; end; rule begin x[0] := false; x[1] := true; end; rule begin x[0] := true; x[1] := false; end; rumur-2020.02.17/tests/boolean-case.m000066400000000000000000000002421362265074000171320ustar00rootroot00000000000000-- Test "boolean" in different cases var x: boolean; y: Boolean; z: BOOLEAN; w: BoOlEaN; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/boolean-const.m000066400000000000000000000015551362265074000173550ustar00rootroot00000000000000/* This model is intended to provoke a bug that was observed on commit * 7eafb5295a1f2f094579ef259b537bd3e4996158. Constants would always be * constructed with no type (i.e. implicitly an unrestricted range). This is * fine for anything except a boolean (non-range) constant. A boolean constant * that is constructed with no type can never be used in a boolean expression * because Rumur thinks it is a numeric value and rejects it. * * If this bug is present, the model will fail code generation with a message * like "condition of if clause is not a boolean expression". If everything is * OK, code generation should complete successfully and the generated verifier * should not find any errors. */ const NOT_TRUE: false; var x: boolean; startstate begin x := true; end; rule begin if NOT_TRUE then x := !x; end; end; rule begin x := !x; end; rumur-2020.02.17/tests/boolean-literal-case.m000066400000000000000000000007171362265074000205730ustar00rootroot00000000000000/* A bug observed in commit f6c5ca123d58d1620ab9faaa804e03e075b6ca62 was that * boolean literals that were not lower case were not understood by Rumur, and * resulted in a syntax error. This model tests that this problem has not been * reintroduced. */ var x: boolean; startstate begin x := true; end; rule begin if TRUE then end; if TrUe then end; if True then end; if FALSE then end; if fAlSe then end; if False then end; x := !x; end; rumur-2020.02.17/tests/boolean-shadow.m000066400000000000000000000003621362265074000175070ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that it is not possible to shadow the 'boolean' type var x: boolean; y: 0 .. 1; startstate begin x := true; end; rule type boolean: 0 .. 1; var z: boolean; begin z := 0; y := z; x := !x; end; rumur-2020.02.17/tests/bound-basic.m000066400000000000000000000003751362265074000167770ustar00rootroot00000000000000-- rumur_flags: ['--bound', '3'] -- a basic test to confirm the --bound option works var x: 0 .. 10; startstate begin x := 0; end; rule begin x := 10 - x; end; rule x > 0 ==> begin x := x - 1; end; rule x < 10 ==> begin x := x + 1; end; rumur-2020.02.17/tests/bound-illegal.m000066400000000000000000000003021362265074000173150ustar00rootroot00000000000000-- rumur_flags: ['--bound', 'foobar'] -- rumur_exit_code: 1 -- test that illegal --bound options are rejected var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/bound-limit.m000066400000000000000000000004151362265074000170270ustar00rootroot00000000000000-- rumur_flags: ['--bound', '3'] -- test a --bound that should prevent an invariant being violated var x: 0 .. 10; startstate begin x := 0; end; rule x < 10 ==> begin x := x + 1; end; rule x > 0 ==> begin x := x - 1; end; invariant "x stays small" x < 4; rumur-2020.02.17/tests/bound-limit2.m000066400000000000000000000004641362265074000171150ustar00rootroot00000000000000-- rumur_flags: ['--bound', '3'] -- checker_exit_code: 1 -- test that we can still violate an invariant that is *just* within the --bound var x: 0 .. 10; startstate begin x := 0; end; rule x < 10 ==> begin x := x + 1; end; rule x > 0 ==> begin x := x - 1; end; invariant "x stays small" x < 3; rumur-2020.02.17/tests/bound-unused.m000066400000000000000000000003621362265074000172150ustar00rootroot00000000000000-- rumur_flags: ['--bound', '10'] /* test that it's OK to set a --bound that does not have an effect because the * model finishes earlier than the bound */ var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/call-no-lvalue.m000066400000000000000000000011721362265074000174200ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model tries several variations on passing a non-lvalue as a var * parameter. We should reject this. On commit * fe9344f5b723608cd8916bd16c2688f9494ca92a, this causes an uncaught exception. */ var x: boolean; function foo(var y: 0 .. 100): boolean; begin assert y = 42; return true; end; function bar(var z: boolean): boolean; begin assert z; return true; end; procedure baz(var w: 0 .. 100); begin assert w = 42; end; procedure qux(var v: boolean); begin assert v; end; startstate begin x := true; end; rule begin foo(42); bar(true); baz(42); qux(true); x := !x; end; rumur-2020.02.17/tests/cex-boolean-startstate.m000066400000000000000000000005111362265074000211710ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'^Startstate\s+1, y:\s*(true|false) fired.$', re.MULTILINE) -- Similar to cex-boolean.m, but using a ruleset startstate. var x: boolean; ruleset y: boolean do startstate begin x := true; end; end; rule begin x := !x; end; invariant x; rumur-2020.02.17/tests/cex-boolean.m000066400000000000000000000005061362265074000170010ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'^Rule\s+1, y:\s*(true|false) fired.$', re.MULTILINE) -- Similar to cex-enum.m, but with the built-in enum 'boolean'. var x: boolean; startstate begin x := true; end; ruleset y: boolean do rule begin x := !x; end; end; invariant x; rumur-2020.02.17/tests/cex-enum-startstate.m000066400000000000000000000005361362265074000205250ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'^Startstate\s+1, y:\s*[AB] fired.$', re.MULTILINE) -- Similar to cex-enum.m, but with the enum parameter in a startstate. type t: enum { A, B}; var x: boolean; ruleset y: t do startstate begin x := true; end; end; rule begin x := !x; end; invariant x; rumur-2020.02.17/tests/cex-enum.m000066400000000000000000000010521362265074000163230ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'^Rule\s+1, y:\s*[AB] fired.$', re.MULTILINE) /* An example that causes a counter-example trace involving an enum. The * motivation for this is that rulesets quantifying over enums led to enums * being printed as numeric values instead of the enum member (observed on * commit 8bf648a180db4070cc5777dd5c73f1a4635c0ab1). */ type t: enum { A, B}; var x: boolean; startstate begin x := true; end; ruleset y: t do rule begin x := !x; end; end; invariant x; rumur-2020.02.17/tests/clear-complex.m000066400000000000000000000002641362265074000173410ustar00rootroot00000000000000-- Test of the clear statement on a complex type var x: record a: boolean end; startstate begin x.a := true; end; rule begin clear x; end; rule begin x.a := true; end; rumur-2020.02.17/tests/clear-simple.m000066400000000000000000000002411362265074000171560ustar00rootroot00000000000000-- Test of the clear statement on a simple type var x: boolean; startstate begin x := true; end; rule begin clear x; end; rule begin x := true; end; rumur-2020.02.17/tests/compare-record.m000066400000000000000000000005541362265074000175120ustar00rootroot00000000000000-- test comparison between record types type t: record x: boolean; end; var x: t; y: t; startstate begin x.x := true; y.x := true; end; rule begin -- test comparison in an if statement if x = y then end; -- test negative comparison if x != y then end; -- test in an assertion assert x = y; x.x := !x.x; y.x := !y.x; end; rumur-2020.02.17/tests/const-enum.m000066400000000000000000000005621362265074000166770ustar00rootroot00000000000000-- Various unusual usages of a const with enum type type t: enum { A, B, C }; const X: A; var x: t; startstate begin x := A; end; rule begin x := B; end; rule begin x := X; end; rule begin if x = X then x := C; end; end; rule begin switch X case A: x := B; end; end; rule begin switch X case X: x := X; end; end; rumur-2020.02.17/tests/cover-basic.m000066400000000000000000000002161362265074000170000ustar00rootroot00000000000000-- Test of a basic cover property var x: boolean; startstate begin x := true; end; rule begin x := !x; end; cover "x was false" !x; rumur-2020.02.17/tests/cover-basic2.m000066400000000000000000000005061362265074000170640ustar00rootroot00000000000000-- checker_output: None if self.xml else re.compile(r'^\s*cover "x was false" hit 2 times$', re.MULTILINE) -- Test of a cover property that is hit multiple times var x: boolean; y: boolean; startstate begin x := true; y := true; end; rule begin x := !x; end; rule begin y := !y; end; cover "x was false" !x; rumur-2020.02.17/tests/cover-miss.m000066400000000000000000000004101362265074000166660ustar00rootroot00000000000000-- checker_output: None if self.xml else re.compile(r'^\s*cover "x was 2" not hit$', re.MULTILINE) -- checker_exit_code: 1 -- Test a cover that is never hit var x: 0 .. 2; startstate begin x := 0; end; rule begin x := 1 - x; end; cover "x was 2" x = 2; rumur-2020.02.17/tests/cover-multiple.m000066400000000000000000000004651362265074000175600ustar00rootroot00000000000000-- checker_output: None if self.xml else re.compile(r'^\s*cover "x was 2" not hit$', re.MULTILINE) -- checker_exit_code: 1 -- Test a set of multiple covers var x: 0 .. 2; startstate begin x := 0; end; rule begin x := 1 - x; end; cover "x was 0" x = 0; cover "x was 1" x = 1; cover "x was 2" x = 2; rumur-2020.02.17/tests/cover-stmt-miss.m000066400000000000000000000004241362265074000176600ustar00rootroot00000000000000-- checker_output: None if self.xml else re.compile(r'^\s*cover "x was 2" not hit$', re.MULTILINE) -- checker_exit_code: 1 -- Test a cover statement that is never hit var x: 0 .. 2; startstate begin x := 0; end; rule begin x := 1 - x; cover "x was 2" x = 2; end; rumur-2020.02.17/tests/cover-stmt.m000066400000000000000000000004031362265074000167040ustar00rootroot00000000000000-- checker_output: None if self.xml else re.compile(r'^\s*cover "x was true" hit 1 times$', re.MULTILINE) -- Test a cover property used as a statement var x: boolean; startstate begin x := true; end; rule begin x := !x; cover "x was true" x; end; rumur-2020.02.17/tests/cover-trivial.m000066400000000000000000000003621362265074000173730ustar00rootroot00000000000000-- checker_output: None if self.xml else re.compile(r'^\s*cover "dummy" hit 2 times$', re.MULTILINE) -- Test a degenerate cover property var x: boolean; startstate begin x := true; end; rule begin x := !x; end; cover "dummy" true; rumur-2020.02.17/tests/diff-trace-arrays.m000066400000000000000000000016101362265074000201050ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off', '--threads', '1'] -- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'(?!(.|\n)*\b(s\[D\]:\s*3)\b(.|\n)*\b\2\b)') /* This model tests for regression of a bug first observed on commit * 7e5654ddb9ad4f3342d3c1b1abef9a3402df5dee. Certain models using enums to index * arrays which produced a failing counter-example, would result in a diff trace * that repeatedly listed an unchanging value for some members of the array. If * the bug has been re-introduced, this model will print the assignment 'S[D]:3' * multiple times in the trace. */ type t: enum { A, B, C, D, E }; d: 0 .. 4; var s: array[t] of d; startstate begin end; ruleset i: t; v: d do rule isundefined(s[i]) & forall j: t do j = i | isundefined(s[j]) | s[j] < v end ==> begin s[i] := v; end; end; invariant exists i: t do isundefined(s[i]) end rumur-2020.02.17/tests/differing-range-passed-to-function.m000066400000000000000000000002021362265074000233450ustar00rootroot00000000000000var x: 0 .. 10; procedure foo(y: 0 .. 5); begin end; startstate begin x := 0; end; rule begin foo(x); x := 1 - x; end; rumur-2020.02.17/tests/differing-type-return.m000066400000000000000000000007101362265074000210330ustar00rootroot00000000000000-- The following tests a case where we return a variable of a range type that -- differs from the function's return type. This does not exceed the type bounds -- and should be accepted -- it is accepted by CMurphi -- but Rumur at commit -- 84998945e6f0f2f3d15b2c9d3bb50e953ffb5143 rejects it. type t1: 0 .. 10; t2: 0 .. 11; var x: t1; function foo(): t2; begin return x; end; startstate begin x := 0; end; rule begin x := 10 - foo(); end; rumur-2020.02.17/tests/differing-type-return3.m000066400000000000000000000004361362265074000211230ustar00rootroot00000000000000-- A variant of differing-type-return.m where we use the same type for both -- entities but indirect through a type definition. type t1: 0 .. 10; t2: t1; var x: t2; function foo(): t1; begin return x; end; startstate begin x := 0; end; rule begin x := 10 - foo(); end; rumur-2020.02.17/tests/disabled/000077500000000000000000000000001362265074000161755ustar00rootroot00000000000000rumur-2020.02.17/tests/disabled/differing-type-return2.m000066400000000000000000000011521362265074000226650ustar00rootroot00000000000000-- checker_exit_code: 1 -- A variant of differing-type-return.m that should be accepted for code -- generation but then should fail at runtime. -- FIXME: *Should* this actually fail at runtime? The generated code currently -- results in a function that returns an rvalue (value_t), so everything works -- out fine. If the user had actually written this, it would be clear what they -- meant. I'm inclined to think maybe this should just be accepted...? type t1: 0 .. 10; t2: 0 .. 11; var x: t2; function foo(): t1; begin return x; end; startstate begin x := 0; end; rule begin x := 11 - foo(); end; rumur-2020.02.17/tests/disabled/rule-no-begin.m000066400000000000000000000013261362265074000210200ustar00rootroot00000000000000/* The following model contains a simple rule with no 'begin' token. According * to the Murphi grammar [0] this is valid, but neither CMurphi nor Rumur at * time of writing (commit 5222f6ddce51ea66ceda6ecb0e016a94308e835b) are able to * parse this model. The root cause is that this part of the grammar is * ambiguous to an LALR(1) parser, which is what both CMurphi and Rumur are * using. It would be nice to address this weakness in future, but to do so I * think we will need to move to a GLR parser. * * Github: see also #77 "begin on rules is not optional" * * [0]: https://www.cs.ubc.ca/~ajh/courses/cpsc513/assign-token/User.Manual */ var x: boolean; startstate x := true; end; rule x := !x; end; rumur-2020.02.17/tests/disabled/smt-array-compare.m000066400000000000000000000010701362265074000217140ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None /* This is an example that contains a tautology. The SMT bridge should be able * to simplify this to `true`, but it currently cannot. The cause is that we * over-approximate ranges used as array indexes and values. So the SMT bridge * does not know that the values of the array can only be 1. */ var x: array[0 .. 1] of 1 .. 1; y: array[0 .. 1] of 1 .. 1; z: boolean; startstate begin z := true; end; rule begin if x = y then z := !z; end; end; rumur-2020.02.17/tests/disabled/smt-div2.m000066400000000000000000000006261362265074000200240ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with division by non-constant var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if 2 * x / x = 2 then y := !y; end; end; rumur-2020.02.17/tests/disabled/smt-mod2.m000066400000000000000000000006211362265074000200140ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with modulo by a non-constant var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x % x = 0 then y := !y; end; end; rumur-2020.02.17/tests/disabled/zebra-puzzle.m000066400000000000000000000110651362265074000210100ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off'] /* This model is an experiment in using Rumur to solve the zebra puzzle * (https://en.wikipedia.org/wiki/Zebra_Puzzle). The model should eventually * fail the invariant with a counterexample that ends in the solution to the * puzzle. */ type nationality: enum { ENGLISHMAN, SPANIARD, UKRANIAN, JAPANESE, NORWEGIAN } colour: enum { RED, GREEN, IVORY, YELLOW, BLUE } animal: enum { DOG, SNAIL, FOX, HORSE, ZEBRA } cigarette: enum { OLDGOLD, CHESTERFIELDS, KOOLS, LUCKYSTRIKE, PARLIAMENTS } drink: enum { COFFEE, TEA, MILK, ORANGEJUICE, WATER } house: record citizenship: nationality paint: colour pet: animal brand: cigarette beverage: drink end var houses: array [0 .. 4] of house startstate begin end ruleset i: 0 .. 4 do -- define rules to make arbitrary assignments for each house ruleset n: nationality do rule isundefined(houses[i].citizenship) ==> begin houses[i].citizenship := n; end end ruleset c: colour do rule isundefined(houses[i].paint) ==> begin houses[i].paint := c; end end ruleset a: animal do rule isundefined(houses[i].pet) ==> begin houses[i].pet := a; end end ruleset c: cigarette do rule isundefined(houses[i].brand) ==> begin houses[i].brand := c; end end ruleset d: drink do rule isundefined(houses[i].beverage) ==> begin houses[i].beverage := d; end end -- define assumptions to capture the rules of the puzzle assume "the Englishman lives in the red house" !isundefined(houses[i].citizenship) & !isundefined(houses[i].paint) & houses[i].citizenship = ENGLISHMAN -> houses[i].paint = RED assume "the Spaniard owns the dog" !isundefined(houses[i].citizenship) & !isundefined(houses[i].pet) & houses[i].citizenship = SPANIARD -> houses[i].pet = DOG assume "coffee is drunk in the green house" !isundefined(houses[i].paint) & !isundefined(houses[i].beverage) & houses[i].beverage = COFFEE -> houses[i].paint = GREEN assume "the Ukranian drinks tea" !isundefined(houses[i].citizenship) & !isundefined(houses[i].beverage) & houses[i].citizenship = UKRANIAN -> houses[i].beverage = TEA assume "the green house is immediately to the right of the ivory house" forall j: 0 .. 4 do !isundefined(houses[i].paint) & houses[i].paint = GREEN & !isundefined(houses[j].paint) & houses[j].paint = IVORY -> i = j + 1 end assume "the Old Gold smoker owns snails" !isundefined(houses[i].brand) & !isundefined(houses[i].pet) & houses[i].brand = OLDGOLD -> houses[i].pet = SNAIL assume "Kools are smoked in the yellow house" !isundefined(houses[i].brand) & !isundefined(houses[i].paint) & houses[i].brand = KOOLS -> houses[i].paint = YELLOW assume "milk is drunk in the middle house" !isundefined(houses[i].beverage) & houses[i].beverage = MILK -> i = 2 assume "the Norwegian lives in the first house" !isundefined(houses[i].citizenship) & houses[i].citizenship = NORWEGIAN -> i = 0 assume "the man who smnokes Chesterfields lives in the house next to the man with the fox" forall j: 0 .. 4 do !isundefined(houses[i].brand) & !isundefined(houses[j].pet) & houses[i].brand = CHESTERFIELDS & houses[j].pet = FOX -> i = j + 1 | j = i + 1 end assume "Kools are smoked in the house next to the house where the horse is kept" forall j: 0 .. 4 do !isundefined(houses[i].brand) & !isundefined(houses[j].pet) & houses[i].brand = KOOLS & houses[j].pet = HORSE -> i = j + 1 | j = i + 1 end assume "the Lucky Strike smoker drinks orange juice" !isundefined(houses[i].brand) & !isundefined(houses[i].beverage) & houses[i].brand = LUCKYSTRIKE -> houses[i].beverage = ORANGEJUICE assume "the Japanese smokes Parliaments" !isundefined(houses[i].citizenship) & !isundefined(houses[i].brand) & houses[i].citizenship = JAPANESE -> houses[i].brand = PARLIAMENTS assume "the Norwegian lives next to the blue house" forall j: 0 .. 4 do !isundefined(houses[i].citizenship) & !isundefined(houses[j].paint) & houses[i].citizenship = NORWEGIAN & houses[j].paint = BLUE -> i = j + 1 | j = i + 1 end end -- terminate as soon as everything in the model has been chosen invariant exists i: 0 .. 4 do isundefined(houses[i].citizenship) | isundefined(houses[i].paint) | isundefined(houses[i].pet) | isundefined(houses[i].brand) | isundefined(houses[i].beverage) end rumur-2020.02.17/tests/division.m000066400000000000000000000004051362265074000164270ustar00rootroot00000000000000-- If you can believe it, my initial implementation left out the division -- operator. We have this test case just to make sure it works. var x: 0 .. 2; startstate begin x := 2; end; rule begin x := x / 2; end; rule x < 2 ==> begin x := x + 1; end; rumur-2020.02.17/tests/double-semicolon.m000066400000000000000000000005041362265074000200430ustar00rootroot00000000000000/* This model contains an extraneous semi-colon. On commit * 90a95ea10ef3eeb15803954c0f8c9fe267799b94, it was observed that Rumur * incorrectly rejects this as a syntax error. This tests whether the problem * has been reintroduced. */ var x: boolean; startstate begin x := false; end; rule begin x := !x;; end; rumur-2020.02.17/tests/double-semicolon2.m000066400000000000000000000002411362265074000201230ustar00rootroot00000000000000-- similar to double-semicolon.m, but using doubles in other places var x: boolean; ; startstate begin x := false; end;;; rule begin x := !x;; end; ; ; rumur-2020.02.17/tests/duplicate-enum-members.m000066400000000000000000000005071362265074000211520ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model tests for the presence of a bug that was observed on commit * cf02a36f1c2f4a3726aba23aaa857ed90914ba54, wherein an enum type with duplicate * members is incorrectly accepted. */ type t: enum { A, A }; var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/duplicate-enum-members2.m000066400000000000000000000003741362265074000212360ustar00rootroot00000000000000-- rumur_exit_code: 1 /* A variation on duplicate-enum-members.m where we use members in different * types that collide. */ type t1: enum { A }; t2: enum { A }; var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/duplicate-record-fields.m000066400000000000000000000004371362265074000213020ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test for duplicate field names within a record. This should fail to generate -- a checker. type foo_t: record a: boolean; a: 0 .. 2; end; var x: boolean; y: foo_t; startstate begin x := true; y.a := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/duplicate-startstate.m000066400000000000000000000004211362265074000207470ustar00rootroot00000000000000-- In earlier incarnations of Rumur, models like this would trigger a memory -- leak as the duplicate startstate that was encountered would not be freed. var x: boolean; startstate begin x := true; end; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/duplicate-state-fields.m000066400000000000000000000003141362265074000211360ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test for duplicate names within the state. This should fail to generate a -- checker. var a: boolean; a: 0 .. 2; startstate begin a := 0; end; rule begin a := 0; end; rumur-2020.02.17/tests/enum-local-var.m000066400000000000000000000004051362265074000174250ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] -- This model previously triggered a bug wherein Rumur would segfault during -- code generation. type en: enum { A, B }; var x: en; startstate begin x := A; end; rule var y: en; begin y := A; end; rumur-2020.02.17/tests/error-statement.m000066400000000000000000000002421362265074000177350ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test a basic error statement. var x: boolean; startstate begin x := true; end; rule begin x := !x; error "hello world"; end; rumur-2020.02.17/tests/error-string-injection.m000066400000000000000000000007651362265074000212310ustar00rootroot00000000000000/* This model checks we have not reintroduced a problem first observed on commit * e1b74738ba73df5352622464be47b69db688c633, wherein invariant names involving * printf format codes would cause bad calls to the function error() to be * generated in the verifier. If this problem has been introduced, the generated * model will fail to compile with the given flags. */ var x: boolean; startstate begin x := true; end; rule begin x := !x; end; invariant "test string %s injection" x | !x; rumur-2020.02.17/tests/escaping-expressions.m000066400000000000000000000007721362265074000207630ustar00rootroot00000000000000-- checker_exit_code: 1 /* This model intentionally provokes an out-of-range indexing error, but with an * expression that contains a character that is sensitive in XML. When using the * machine-readable output format on commit * 5ea35510cacf551929203e947922ad5811a639d1 this produced invalid XML. This * tests whether the bug involved has been reintroduced. */ var x: array[0 .. 1] of boolean; startstate begin x[0] := true; x[1] := false; end; rule begin x[3 < 2 ? 3 : 2] := !x[0]; end; rumur-2020.02.17/tests/for-step-0-dynamic.m000066400000000000000000000005211362265074000201200ustar00rootroot00000000000000-- checker_exit_code: 1 -- this model tests that a step in a for loop that is not a generation-time -- constant but ends up being 0 at runtime is caught during checking var y: boolean; startstate begin y := true; end; rule var x: 0 .. 10; begin x := 0; for i := 0 to 2 by x do put "hello world"; end; y := !y; end; rumur-2020.02.17/tests/for-step-0.m000066400000000000000000000003521362265074000165000ustar00rootroot00000000000000-- rumur_exit_code: 1 -- this model tests that a trivial infinite loop is rejected var x: boolean; startstate begin x := true; end; rule begin -- this loop will never finish for i := 0 to 10 by 0 do end; x := !x; end; rumur-2020.02.17/tests/for-step-1.m000066400000000000000000000003471362265074000165050ustar00rootroot00000000000000-- rumur_exit_code: 1 -- this model tests that infinite down-loops are rejected var x: boolean; startstate begin x := true; end; rule begin -- this loop will never finish for i := 10 to 0 by 1 do end; x := !x; end; rumur-2020.02.17/tests/for-step-neg-1.m000066400000000000000000000003731362265074000172530ustar00rootroot00000000000000-- rumur_exit_code: 1 -- this model tests that a loop iterating in the wrong direction is rejected var x: boolean; startstate begin x := true; end; rule begin -- this loop will never finish for i := 0 to 10 by -1 do end; x := !x; end; rumur-2020.02.17/tests/for-step-neg.m000066400000000000000000000002721362265074000171130ustar00rootroot00000000000000-- this model tests that we can write loops that iterate backwards var x: boolean; startstate begin x := true; end; rule begin for i := 9 to 1 by -1 do x := !x; end; end; rumur-2020.02.17/tests/for-variants.m000066400000000000000000000056141362265074000172250ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off'] -- this model tests a variety of for-loop expressions, as some of these have -- proven problematic in the past type t1: 0 .. 2; var x: t1; y: t1; z: 0 .. 3; a: array[t1] of t1; startstate begin -- loop by type for i: t1 do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop by inline type for i: 0 .. 2 do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop by range for i := 0 to 2 do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop that crosses 0 for i := -1 to 1 do a[i + 1] := 1 - i; end; assert a[0] = 2 & a[1] = 1 & a[2] = 0; undefine a; -- range that ends at 0 for i := -2 to 0 do a[-i] := -i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- range that is fully negative for i := -3 to -1 do a[3 + i] := 3 + i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop with a step for i := 0 to 2 by 1 do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop with a non-1 step for i := 0 to 2 by 2 do a[i] := i; end; assert a[0] = 0 & isundefined(a[1]) & a[2] = 2; undefine a; -- loop that does not start at 0 for i := 1 to 2 do a[i] := i; end; assert isundefined(a[0]) & a[1] = 1 & a[2] = 2; undefine a; -- lower and upper bound equal for i := 0 to 0 do a[i] := i; end; assert a[0] = 0 & isundefined(a[1]) & isundefined(a[2]); undefine a; -- loop whose last step exceeds the upper bound for i := 0 to 2 by 3 do a[i] := i; end; assert a[0] = 0 & isundefined(a[1]) & isundefined(a[2]); undefine a; -- loop that has a variable lower bound x := 0; for i := x to 2 do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop that has a variable upper bound y := 2; for i := 0 to y do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop that has a variable lower and upper bound x := 0; y := 2; for i := x to y do a[i] := i end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- FIXME: the cases below currently fail -- loop with variable step z := 1; for i := 0 to 2 by z do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop with variable lower bound and step x := 0; z := 1; for i := x to 2 by z do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop with variable upper bound and step y := 2; z := 1; for i := 0 to y by z do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; -- loop with variable bounds and step x := 0; y := 2; z := 1; for i := x to y by z do a[i] := i; end; assert a[0] = 0 & a[1] = 1 & a[2] = 2; undefine a; end; rumur-2020.02.17/tests/fox-goose-beans.m000066400000000000000000000050151362265074000176010ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off'] -- checker_exit_code: 1 /* This is a model of the "Fox, goose and bag of beans puzzle" * https://en.wikipedia.org/wiki/Fox,_goose_and_bag_of_beans_puzzle * * We represent the location of each of the parties (fox, goose, beans and human * chaperone) as either side of the river (east or west) or in the boat itself. * All parties start on the east side of the river. The object is to get all * parties to the west side of the river. The boat can only carry the human by * themselves or with one other party. All of this we encode as transition * rules. * * The two failure cases (the fox eats the goose when left alone with it and the * goose eats the beans when left alone with them) are encoded as assumptions. * The *negation* of the goal itself is encoded as an invariant. Thus what we * should find is that this model *fails*, producing a counterexample trace that * gives a solution to the puzzle. * * One such solution is: * 1. the human crosses to west with the goose * 2. the human crosses back east with nothing * 3. the human crosses west with the beans * 4. the human crosses back east with the goose * 5. the human crosses west with the fox * 6. the human crosses back east with nothing * 7. the human crosses west with goose */ type location: enum { EAST, BOAT, WEST }; var fox: location; goose: location; beans: location; human: location; startstate begin fox := EAST; goose := EAST; beans := EAST; human := EAST; end; rule "set out with fox" fox = human & human != BOAT ==> begin human := BOAT; fox := BOAT; end; rule "set out with goose" goose = human & human != BOAT ==> begin human := BOAT; goose := BOAT; end; rule "set out with beans" beans = human & human != BOAT ==> begin human := BOAT; beans := BOAT; end; rule "set out alone" human != BOAT ==> begin human := BOAT; end; rule "arrive east" human = BOAT ==> begin human := EAST; if fox = BOAT then fox := EAST; end; if goose = BOAT then goose := EAST; end; if beans = BOAT then beans := EAST; end; end; rule "arrive west" human = BOAT ==> begin human := WEST; if fox = BOAT then fox := WEST; end; if goose = BOAT then goose := WEST; end; if beans = BOAT then beans := WEST; end; end; assume "fox prevented from eating goose" fox = goose -> human = fox; assume "goose prevented from eating beans" goose = beans -> human = goose; invariant "goal" fox != WEST | goose != WEST | beans != WEST | human != WEST; rumur-2020.02.17/tests/function-in-guard.m000066400000000000000000000022271362265074000201400ustar00rootroot00000000000000/* This model is intended to provoke an issue that was observed on commit * 7eafb5295a1f2f094579ef259b537bd3e4996158. If the issue has been reintroduced, * the generated verifier for this model will cause a warning when compiled * about passing a const object as a non-const parameter. * * The cause of this was that the state is always passed in as the first * parameter to a model function. Specifically, it is passed as a non-const * parameter. Meanwhile, the state variable that is available in the context of * a rule guard is const. Obviously the function "ok" is safe to call in the * guard below because it does not modify any state variables. * * A compiler warning may not seem like a particularly significant issue, but * one of the secondary goals of Rumur is for the generated verifier to always * compile warning-free. The advantage of this is that you can build your * generated verifier with "-W -Wall -Wextra" and any warnings you see are * likely to indicate actual problems in your input model. */ var x: boolean; function ok(): boolean; begin return true; end; startstate begin x := true; end; rule ok() ==> begin x := !x; end; rumur-2020.02.17/tests/function-order.m000066400000000000000000000004541362265074000175450ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that calling a function that is defined later is rejected var x: boolean; function foo(n: boolean): boolean; begin return bar(n); end; function bar(n: boolean): boolean; begin return n; end; startstate begin x := true; end; rule begin x := !foo(x); end; rumur-2020.02.17/tests/function-param-intact.m000066400000000000000000000006111362265074000210050ustar00rootroot00000000000000/* On commit 14de19ce3bb545ddb4857fd91cc3a17a4fa2fccb, it was observed that * scalar values being passed to non-var function parameters were being * calculated incorrectly. This model checks that this bug has not been * reintroduced. */ var x: 1 .. 3; procedure foo(y: 1 .. 3); begin assert y < 3; end; startstate begin x := 1; end; rule begin foo(x); x := 2 - (x - 1); end; rumur-2020.02.17/tests/function-return-ignored.m000066400000000000000000000004431362265074000213740ustar00rootroot00000000000000/* Test calling a function as if it were a procedure (function that does not * return a value). Note, CMurphi does not support this extension. */ var x: boolean; function foo(): boolean; begin return true; end; startstate begin x := true; end; rule begin x := !x; foo(); end; rumur-2020.02.17/tests/function1.m000066400000000000000000000004331362265074000165120ustar00rootroot00000000000000-- Preliminary test of complex-returning function implementation -- Yes, the model below makes no sense. type t: record x: boolean; end; var y: t; function foo(): t; begin return y; end; startstate begin y.x := true; end; rule begin y := foo(); y.x := !y.x; end; rumur-2020.02.17/tests/identifier-case.m000066400000000000000000000003271362265074000176410ustar00rootroot00000000000000-- This test is designed to provoke any issues with handling the -- case-sensitivity of variables. var x: boolean; startstate begin x := true; end; ruleset X: boolean do rule begin x := !X; end; end; rumur-2020.02.17/tests/identifier-case2.m000066400000000000000000000003311362265074000177160ustar00rootroot00000000000000-- This test is designed to provoke any issues with handling the -- case-sensitivity of variables. var x: boolean; startstate begin x := true; end; ruleset X: 0 .. 2 do rule begin x := X = 0; end; end; rumur-2020.02.17/tests/identifier-case3.m000066400000000000000000000004201362265074000177160ustar00rootroot00000000000000-- This test is designed to provoke any issues with handling the -- case-sensitivity of variables. var x: boolean; startstate begin x := true; end; ruleset X: 0 .. 2 do rule begin if X = 0 then x := true; else x := false; end; end; end; rumur-2020.02.17/tests/illegal-array-index.m000066400000000000000000000005371362265074000204430ustar00rootroot00000000000000-- rumur_exit_code: 1 -- This was an illegal model AFL found that caused a crash using commit -- 5ccd2be57847aa173a4741340b359edcc3b54074. type -- Note: array index bounds are back to front foo_t: array[8 .. 1] of boolean; var x: foo_t; startstate begin x[0] := true; x[1] := false; end; rule begin x[0] := x[1]; x[1] := x[0]; end; rumur-2020.02.17/tests/index-out-of-range.m000066400000000000000000000002441362265074000202140ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test indexing into an array out of bounds. var x: array[0 .. 1] of boolean; startstate begin end; rule begin x[3] := false; end; rumur-2020.02.17/tests/invariant-failure-message.m000066400000000000000000000005111362265074000216430ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'^.*invariant "hello world" failed', re.MULTILINE) /* Test that the checker reports the name of an invariant in its failure * message. */ var x: boolean startstate begin x := true; end rule begin x := !x; end invariant "hello world" false rumur-2020.02.17/tests/invariant-syntax.m000066400000000000000000000002751362265074000201270ustar00rootroot00000000000000-- A model that tests invariants with flipped syntax var x: boolean; startstate begin x := true; end; rule begin x := !x; end; invariant "hello" x | !x; invariant x | !x "world"; rumur-2020.02.17/tests/isundefined-array.m000066400000000000000000000003051362265074000202130ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test of isundefined on arrays, that should not work. var x: array [0 .. 0] of boolean; startstate begin x[0] := true; end; rule begin x[0] := isundefined(x); end; rumur-2020.02.17/tests/isundefined-basic.m000066400000000000000000000004151362265074000201600ustar00rootroot00000000000000-- Basic test of isundefined var x: boolean; startstate begin if isundefined(x) then x := true; else x := false; end; end; rule isundefined(x) ==> begin x := true; end; rule !isundefined(x) ==> begin x := !x; end; rule begin undefine x; end; rumur-2020.02.17/tests/isundefined-decl.m000066400000000000000000000006361362265074000200130ustar00rootroot00000000000000-- Test of isundefined on various decls var x: boolean; function bar(var y: boolean): boolean; begin return isundefined(y); end; function baz(): boolean; var y: boolean; begin return isundefined(y); end; startstate begin x := true; end; rule bar(x) ==> begin x := !x; end; rule baz() ==> begin x := !x; end; rule var y: boolean; begin x := isundefined(y); end; rule begin x := !x; end; rumur-2020.02.17/tests/isundefined-element.m000066400000000000000000000004431362265074000205310ustar00rootroot00000000000000-- Test of isundefined on nested values. var x: array [0 .. 0] of boolean; y: record z: boolean; end; startstate begin x[0] := true; y.z := true; end; rule begin x[0] := isundefined(x[0]); end; rule begin y.z := isundefined(y.z); end; rule begin x[0] := !x[0]; end; rumur-2020.02.17/tests/isundefined-function.m000066400000000000000000000013441362265074000207260ustar00rootroot00000000000000-- Test of using isundefined in various function contexts. type t: record z: boolean; end; var x: boolean; y: t; procedure foo(var a: boolean); begin if isundefined(a) then end; end; procedure bar(a: boolean); begin if isundefined(a) then end; end; function baz(var a: boolean): boolean; begin return isundefined(a); end; function qux(a: boolean): boolean; begin return isundefined(a); end; function quux(a: t): boolean; begin return isundefined(a.z); end; function quuz(var a: t): boolean; begin return isundefined(a.z); end; startstate begin x := true; end; rule begin foo(x); bar(x); baz(x); qux(x); foo(y.z); bar(y.z); baz(y.z); qux(y.z); quux(y); quuz(y); x := !x; end; rumur-2020.02.17/tests/isundefined-record.m000066400000000000000000000003071362265074000203550ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test of isundefined on records, that should not work. var x: record y: boolean; end; startstate begin x.y := true; end; rule begin x.y := isundefined(x); end; rumur-2020.02.17/tests/isundefined-rvalue.m000066400000000000000000000002611362265074000203740ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test of isundefined on rvalues, that should not work. var x: boolean; startstate begin x := true; end; rule begin x := isundefined(true); end; rumur-2020.02.17/tests/isundefined-rvalue2.m000066400000000000000000000002571362265074000204630ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test of isundefined on rvalues, that should not work. var x: boolean; startstate begin x := true; end; rule begin x := isundefined(!x); end; rumur-2020.02.17/tests/keyword-case.m000066400000000000000000000004621362265074000172030ustar00rootroot00000000000000-- This test is designed to provoke any issues with handling the -- case-insensitivity of keywords. var x: boolean; startstate begin x := true; end; rule begin x := !x; end; Rule begin x := !x; end; rUle begin x := !x; end; ruLe begin x := !x; end; rulE begin x := !x; end; RULE begin x := !x; end; rumur-2020.02.17/tests/liveness-in-ruleset.m000066400000000000000000000003511362265074000205200ustar00rootroot00000000000000-- test of using liveness inside a ruleset block var x: 0 .. 10 startstate begin x := 0; end rule x < 10 ==> begin x := x + 1; end rule x > 0 ==> begin x := x - 1; end ruleset y: 0 .. 10 do liveness "x is y" x = y end rumur-2020.02.17/tests/liveness-in-ruleset2.m000066400000000000000000000005411362265074000206030ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'liveness property "x is y" violated') -- variant of liveness-in-ruleset.m designed to fail var x: 0 .. 10 startstate begin x := 0; end rule x < 9 ==> begin x := x + 1; end rule x > 0 ==> begin x := x - 1; end ruleset y: 0 .. 10 do liveness "x is y" x = y end rumur-2020.02.17/tests/liveness-miss1.m000066400000000000000000000005021362265074000174630ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'liveness property "x is 10" violated') -- test of a liveness property that is not hit var x: 0 .. 10 startstate begin x := 0; end rule x < 9 ==> begin x := x + 1; end rule x > 0 ==> begin x := x - 1; end liveness "x is 10" x = 10 rumur-2020.02.17/tests/liveness-miss2.m000066400000000000000000000005251362265074000174710ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'liveness property "x is 10" violated') -- a variant of liveness-miss1.m that leaves two values unreached var x: 0 .. 10 startstate begin x := 0; end rule x < 8 ==> begin x := x + 1; end rule x > 0 ==> begin x := x - 1; end liveness "x is 10" x = 10 rumur-2020.02.17/tests/liveness-miss3.m000066400000000000000000000005521362265074000174720ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'liveness property "x is 7" violated') -- a variant of liveness-miss1.m that covers all values but where 7 is not live var x: 0 .. 10 startstate begin x := 0; end rule x < 10 ==> begin x := x + 1; end rule x > 0 & x != 8 ==> begin x := x - 1; end liveness "x is 7" x = 7 rumur-2020.02.17/tests/liveness-statement.m000066400000000000000000000003751362265074000204430ustar00rootroot00000000000000-- rumur_exit_code: 1 /* A liveness property makes no sense in a statement (as opposed to the top * level) and should be rejected. */ var x: boolean; startstate begin x := true; end; rule begin x := !x; liveness "not allowed" x | !x; end; rumur-2020.02.17/tests/lock-freedom-i386.py000077500000000000000000000020611362265074000200400ustar00rootroot00000000000000#!/usr/bin/env python3 ''' Version of lock-freedom.py for 32-bit compilation on x86-64 ''' import os import platform import sys import subprocess # this test is only relevant on x86-64 if platform.machine() not in ('amd64', 'x86_64'): print('only relevant for x86-64 machines') sys.exit(125) # check that we have a multilib compiler capable of targeting i386 CC = os.environ.get('CC', 'cc') argv = [CC, '-std=c11', '-m32', '-o', os.devnull, '-x', 'c', '-'] program = '#include \n' \ 'int main(void) {\n' \ ' printf("hello world\\n");\n' \ ' return 0;\n' \ '}\n' p = subprocess.Popen(argv, stdin=subprocess.PIPE, stderr=subprocess.DEVNULL) p.communicate(program.encode('utf-8', 'replace')) if p.returncode != 0: print('compiler cannot target 32-bit code') sys.exit(125) # call the generic lock-freedom.py with some customisation lock_freedom_py = os.path.join(os.path.dirname(__file__), 'lock-freedom.py') sys.exit(subprocess.call(['python3', lock_freedom_py, '-m32'])) rumur-2020.02.17/tests/lock-freedom-x86-64.py000077500000000000000000000007601362265074000202270ustar00rootroot00000000000000#!/usr/bin/env python3 ''' x86-64 version of lock-freedom.py, which needs -mcx16 ''' import os import platform import sys import subprocess # this test is only relevant on x86-64 if platform.machine() not in ('amd64', 'x86_64'): print('only relevant for x86-64 machines') sys.exit(125) # call the generic lock-freedom.py with some customisation lock_freedom_py = os.path.join(os.path.dirname(__file__), 'lock-freedom.py') sys.exit(subprocess.call(['python3', lock_freedom_py, '-mcx16'])) rumur-2020.02.17/tests/lock-freedom.py000066400000000000000000000026771362265074000173630ustar00rootroot00000000000000#!/usr/bin/env python3 ''' Test that a compiled verifier does not depend on libatomic. Uses of __atomic built-ins and C11 atomics can sometimes cause the compiler to emit calls to libatomic instead of inline instructions. This is a problem because we use these in the verifier to implement lock-free algorithms, while the libatomic implementations take locks, defeating the purpose of using them. This test checks that we end up with no libatomic calls in the compiled verifier. ''' import os import platform import re import sys import subprocess # generate a checker for a simple model model = 'var x: boolean; startstate begin x := false; end; rule begin x := !x; end;' argv = ['rumur', '--output', '/dev/stdout'] p = subprocess.Popen(argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE) model_c, _ = p.communicate(model.encode('utf-8', 'replace')) if p.returncode != 0: print('call to rumur failed') sys.exit(1) # compile it to assembly CC = os.environ.get('CC', 'cc') argv = [CC, '-O3', '-std=c11', '-x', 'c', '-', '-S', '-o', '/dev/stdout'] \ + sys.argv[1:] p = subprocess.Popen(argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE) model_s, _ = p.communicate(model_c) if p.returncode != 0: print('compilation failed') sys.exit(1) # check for calls to libatomic functions if re.search('__atomic_', model_s.decode('utf-8', 'replace')) is not None: print('libatomic calls in generated code were not optimised out') sys.exit(-1) # pass sys.exit(0) rumur-2020.02.17/tests/loop-variable-nonzero-start.m000066400000000000000000000010211362265074000221550ustar00rootroot00000000000000-- This model tests for the presence of a bug first observed on commit -- fa71679f33aec0cceafb7528d24c03e3b2d315f3. The limits used for unpacking the -- handle related to the loop variable were incorrect, causing loops like the -- one in the following model to turn into no-ops. If this bug has been -- reintroduced, this model will deadlock. var x: 0 .. 10; startstate begin x := 0; end; rule x = 0 ==> begin for y := 5 to 9 do x := x + 1; end; end; rule x = 5 ==> begin x := 0; end; invariant x = 0 | x = 5; rumur-2020.02.17/tests/math-operators.m000066400000000000000000000003751362265074000175560ustar00rootroot00000000000000-- Test support for some of the alternative operators var x: 0 .. 10; startstate begin x := 0; end; rule x = 0 ∨ x = 1 ==> begin x := x + 1; end; rule ¬(x = 0) ∧ ¬(x = 1) ==> begin x := x - 1; end; invariant x = 0 ∨ ¬(x = 0) rumur-2020.02.17/tests/mixed-aliases.m000066400000000000000000000021631362265074000173330ustar00rootroot00000000000000-- Test of all sorts of alias statement variations. var x: boolean; y: record z: boolean end; procedure foo(a: boolean; var b: boolean); begin -- Alias of a read-only parameter alias w: a do b := !w; end; -- Alias of a writable parameter alias w: b do w := !w; end; end; -- As above for a function, just to make sure there's nothing odd there function bar(a: boolean; var b: boolean): boolean begin alias w: a do b := !w; end; alias w: b do w := !w; end; -- Let's try returning an alias too just for kicks alias w: a do return w; end; -- Unreachable, but test generation of returning an lvalue-eligible alias alias w: b do return w; end; end; startstate begin x := true; y.z := true; end; rule begin -- Alias of a state variable alias w: x do w := !w; end; -- Alias of a complex state variable alias w: y do w.z := !w.z; end; -- Alias of a non-top-level state variable alias w: y.z do w := !w; end; end; ruleset c: boolean do rule begin -- Alias a ruleset parameter alias w: c do x := w; end; end; end; rumur-2020.02.17/tests/multiple-const-decl.m000066400000000000000000000002311362265074000204640ustar00rootroot00000000000000-- Test declaration of multiple constants at once. const A, B: 10; var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/multiple-deadlocks.m000066400000000000000000000023511362265074000203670ustar00rootroot00000000000000-- rumur_flags: ['--threads', '1', '--max-errors', '2'] -- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'^Startstate 1 fired\.\nx:0\n----------\n\nRule "foo" fired\.\nx:1\n----------\n\nEnd of the error trace\.(.|\n)*^Startstate 1 fired\.\nx:0\n----------\n\nRule "bar" fired\.\nx:2\n----------\n\nEnd of the error trace\.', re.MULTILINE) /* This model is designed to trigger a bug that was observed on commit * 0f66cb1de6e4eb359fc4aa1ab4d55c8dd951a04d. When attempting to report multiple * errors and finding a deadlock, the same deadlock would simply be repeated for * until the error count had been reached. If this bug has been re-introduced, * model will produce the same error trace containing "foo" twice, instead of * the two possible deadlocks in this model. * * The option tweaking above is intended to... * --threads 1: run single threaded to ensure the deadlocks are found in a * deterministic order. * --max-errors 2: look for more than one error, a necessary condition for * triggering the bug. * checker_output: match both different traces. */ var x: 0 .. 10; startstate begin x := 0; end; rule "foo" x = 0 ==> begin x := x + 1; end; rule "bar" x = 0 ==> begin x := x + 2; end; rumur-2020.02.17/tests/multiple-errors.m000066400000000000000000000006311362265074000177510ustar00rootroot00000000000000-- checker_exit_code: 1 -- A model with multiple errors to test --max-errors option -- TODO: tweak cmd line args to this and regex the output during testing var x: 0 .. 5; startstate begin x := 0; end; rule x < 4 ==> begin x := x + 2; end; rule x > 1 ==> begin x := x - 2; end; rule x < 5 ==> begin x := x + 1; end; rule x > 0 ==> begin x := x - 1; end; invariant x != 5; invariant x != 4; rumur-2020.02.17/tests/multiple-parameters.m000066400000000000000000000002411362265074000205750ustar00rootroot00000000000000var x: boolean; function foo(a: boolean; b: boolean): boolean; begin return a; end; startstate begin x := true; end; rule begin x := !foo(x, x); end; rumur-2020.02.17/tests/multiple-parameters2.m000066400000000000000000000003571362265074000206670ustar00rootroot00000000000000-- Same as multiple-parameters.m but omitting a semi-colon (Rumur extension). var x: boolean; function foo(a: boolean b: boolean): boolean; begin return a; end; startstate begin x := true; end; rule begin x := !foo(x, x); end; rumur-2020.02.17/tests/multiple-type-decls.m000066400000000000000000000002511362265074000205040ustar00rootroot00000000000000-- Test declaration of multiple types at once. type A, B: record x: boolean; end; var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/multiplication.m000066400000000000000000000004311362265074000176370ustar00rootroot00000000000000/* An early implementation of Rumur accidentally omitted support for * multiplication. This tests that we actually can parse multiplication. */ var x: 1 .. 10; startstate begin x := 1; end; rule x > 1 ==> begin x := x - 1; end; rule x <= 5 ==> begin x := x * 2; end; rumur-2020.02.17/tests/named-assert.m000066400000000000000000000002371362265074000171710ustar00rootroot00000000000000-- A based assertion with a name given to it var x: boolean; startstate begin x := true; end; rule begin assert x | !x "hello world"; x := !x; end; rumur-2020.02.17/tests/negate-complex.m000066400000000000000000000010361362265074000175140ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This tests whether we have reintroduced a bug first noticed against commit * e196ed43199d6d47d36eb9f225017c2123e294c3. The bug incorrectly allowed complex * types to appear as the argument to a negation. If this bug has been * reintroduced Rumur will generate code for this model that will then fail to * compile, when really it should reject the model from the outset. */ var x: record a: boolean; end; startstate begin x.a := true; end; rule begin x.a := !x.a; end; rule begin x := -x; end; rumur-2020.02.17/tests/negate-value-type.m000066400000000000000000000012051362265074000201360ustar00rootroot00000000000000/* This model attempts to provoke a bug first observed on * b1e28bfc4fe1b042f7e3034a1516cd20df789b51. The issue was that negative * literals are considered as the negation of a positive literal and Rumur would * only look at the inner (positive) literal when determining the value type * (value_t). As a result, for the following model it would incorrectly choose * the value type uint8_t that cannot contain -1. If this bug has been * reintroduced, this model will error with a subtraction overflow. */ var x: 0 .. 9; startstate begin x := 0; end; rule x > 0 ==> begin x := x + -1; end; rule x < 9 ==> begin x := x + 1; end; rumur-2020.02.17/tests/negation-of-range.m000066400000000000000000000004141362265074000201030ustar00rootroot00000000000000/* On commit e196ed43199d6d47d36eb9f225017c2123e294c3 this model is incorrectly * rejected. After we fix this bug, this can be used to validate the bug has not * been re-introduced. */ var x: -5 .. 5; startstate begin x := 1; end; rule begin x := -x; end; rumur-2020.02.17/tests/negative-numbers.m000066400000000000000000000002711362265074000200570ustar00rootroot00000000000000const N: -2; type r1: -1 .. 1; r2: -3 .. -2; r3: N .. 1; var x: r3; startstate begin x := N; end; rule begin if x = N then x := -1; else x := N; end; end; rumur-2020.02.17/tests/no-cex-bug.m000066400000000000000000000006551362265074000165560ustar00rootroot00000000000000-- rumur_flags: ['--counterexample-trace', 'off'] -- checker_exit_code: 1 /* This model tries to provoke a bug first observed on commit * e96fd5201027ea9d5027637f8d42800897171d2e. If this bug has been reintroduced, * generating a verifier with `--counterexample-trace off` will result in C code * that fails to compile. */ var x: 0 .. 9; startstate begin x := 0; end; rule begin x := x + 1; end; invariant x != 7; rumur-2020.02.17/tests/no-cex-bug2.m000066400000000000000000000006141362265074000166330ustar00rootroot00000000000000-- rumur_flags: ['--counterexample-trace', 'off'] /* This model tests for a similar bug to no-cex-bug.m. When a liveness property * was used in combination with no counterexample tracing, C code that failed to * compile would be produced. */ var x: 0 .. 9; startstate begin x := 0; end; rule x > 0 ==> begin x := x - 1; end; rule x < 9 ==> begin x := x + 1; end; liveness x = 7; rumur-2020.02.17/tests/non-boolean-condition.m000066400000000000000000000003141362265074000207750ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test using a non-boolean value in a conditional, which should be rejected. var x: 0 .. 10; startstate begin x := 1; end; rule begin if x then x := 3 - x; end; end; rumur-2020.02.17/tests/non-const-parameters.m000066400000000000000000000007721362265074000206710ustar00rootroot00000000000000/* As observed in commit eb74c568327d08b3619b94e80d6cd91464694f96, Rumur would * fail an assert when generating a model that passes the result of a function * to another function. The following model is designed to trigger this bug if * it is still present. */ type t: record x: boolean; end; var x: boolean; procedure foo(a: t); begin end; function bar(): t; var a: t; begin a.x := true; return a; end; startstate begin x := true; end; rule begin foo(bar()); x := !x; end; rumur-2020.02.17/tests/octal-literal.m000066400000000000000000000002331362265074000173360ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that invalid octal literals are rejected var x: 0 .. 09; startstate begin x := 1; end; rule begin x := 1 - x; end; rumur-2020.02.17/tests/octal-literal2.m000066400000000000000000000005311362265074000174210ustar00rootroot00000000000000-- checker_exit_code: 1 -- test octal literals are interpreted correctly var x: 0 .. 010; startstate begin x := 0; end; rule begin -- if the upper bound of the type was correctly parsed as an octal, it should -- have been set to 8 and we should trigger an error here due to assigning a -- value above this bound x := 9 - x; end; rumur-2020.02.17/tests/only-booleans.m000066400000000000000000000001221362265074000173600ustar00rootroot00000000000000var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/only-enum.m000066400000000000000000000002351362265074000165270ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] type foo_t: enum { A, B }; var x: foo_t; startstate begin x := A; end; rule begin x := B; end; rumur-2020.02.17/tests/only-range-and-array.m000066400000000000000000000003601362265074000205320ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] type range_t: 0 .. 1; array_t: array[range_t] of range_t; var x: array_t; startstate begin x[0] := 0; x[1] := 0; end; rule begin x[0] := 1 - x[1]; x[1] := 1 - x[0]; end; rumur-2020.02.17/tests/only-range-and-untouched-array.m000066400000000000000000000002371362265074000225310ustar00rootroot00000000000000type range_t: 0 .. 1; array_t: array[range_t] of range_t; var x: range_t; y: array_t; startstate begin x := 0; end; rule begin x := 1 - x; end; rumur-2020.02.17/tests/only-range-and-unused-array.m000066400000000000000000000002211362265074000220270ustar00rootroot00000000000000type range_t: 0 .. 1; array_t: array[range_t] of range_t; var x: range_t; startstate begin x := 0; end; rule begin x := 1 - x; end; rumur-2020.02.17/tests/out-of-range-function-parameter.m000066400000000000000000000005431362265074000227120ustar00rootroot00000000000000-- checker_exit_code: 1 /* This model passes a different range into a non-var parameter (something that * should be allowed), but at runtime the value that is passed is outside the * range of the accepting parameter. */ var x: 0 .. 10; procedure foo(y: 0 .. 5); begin end; startstate begin x := 3; end; rule begin x := 10 - x; foo(x); end; rumur-2020.02.17/tests/out-of-range-function-parameter2.m000066400000000000000000000006541362265074000227770ustar00rootroot00000000000000-- checker_exit_code: 1 /* On 466fe5b288aaa45c9970e1391701dd8653f8ab82 it was observed that the * following model results in generated code that causes a compiler warning when * compiling due to unsigned/signed comparison. */ var x: boolean procedure foo(y: -65537 .. -2); begin if y = 0 then end; end; startstate begin x := true; end; rule var z: 65537 .. 65537; begin z := 65537; foo(z); x := !x; end; rumur-2020.02.17/tests/pack-state-off.m000066400000000000000000000002771362265074000174160ustar00rootroot00000000000000-- rumur_flags: ['--pack-state', 'off', '--bound', '100'] -- basic test that using an unpacked state works var x: boolean; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/procedure-call-in-expr.m000066400000000000000000000006541362265074000210720ustar00rootroot00000000000000-- rumur_exit_code: 1 /* This model incorrectly uses a call to a procedure within an expression, as if * the procedure returned a number. In commit * 366762cc01cfbb09193ec23ba9762260b458b016, this triggers a bug where Rumur * thinks this is a valid model and generates a verifier that does not compile. */ var x: 0 .. 10; procedure foo(); begin end; startstate begin x := 1; end; rule begin x := 10 - foo(); end; rumur-2020.02.17/tests/put-stmt.m000066400000000000000000000002421362265074000163770ustar00rootroot00000000000000-- Test put statements get parsed correctly. var x: boolean; startstate begin x := false; end; rule begin put "hello world"; put "\n"; x := !x; end; rumur-2020.02.17/tests/put-stmt2.m000066400000000000000000000004331362265074000164630ustar00rootroot00000000000000-- Test put statements get parsed correctly. var x: 0 .. 10; startstate begin x := 0; end; rule x > 0 ==> begin put "in first rule, x is "; put x; put "\n"; x := x - 1; end; rule x < 10 ==> begin put "in second rule, x is "; put x; put "\n"; x := x + 1; end; rumur-2020.02.17/tests/put-stmt3.m000066400000000000000000000002231362265074000164610ustar00rootroot00000000000000-- Test put statements get parsed correctly. var x: boolean; startstate begin put x; x := false; end; rule begin put x; x := !x; end; rumur-2020.02.17/tests/put-stmt4.m000066400000000000000000000010661362265074000164700ustar00rootroot00000000000000-- Test put statements get parsed correctly. var x: boolean; y: record a: boolean; b: boolean; end; startstate begin put "printing an uninitialised boolean...\n"; put x; put "\n"; x := false; put "printing an initialised boolean...\n"; put x; put "\n"; put "printing an uninitialised record...\n"; put y; put "\n"; y.a := true; put "printing a partially initialised record...\n"; put y; put "\n"; y.b := true; put "printing a fully initialised record...\n"; put y; put "\n"; end; rule begin x := !x; end; rumur-2020.02.17/tests/put-string-injection.m000066400000000000000000000007751362265074000207110ustar00rootroot00000000000000/* This model is designed to trigger a problem first observed on * 0f66cb1de6e4eb359fc4aa1ab4d55c8dd951a04d wherein a crafted call to put could * result in the generation of code that would corrupt its own stack. If this * problem has been re-introduced, the generated code for this model will * trigger a compilation error when passing -Werror=format. */ var s: 0 .. 10; a: array[0 .. 10] of boolean; startstate begin a[0] := true; s := 1; end; rule begin put a[4%s]; a[0] := !a[0]; end; rumur-2020.02.17/tests/quantifier-signed-range.m000066400000000000000000000011771362265074000213220ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'off'] -- checker_output: None if self.xml else re.compile(r'\b3 states\b') /* This model tests that quantifiers that pass over zero (i.e. negative start * and positive end) function correctly. In * 99529844092fcbe1bbbfb3170c7b9a8364a6d055, a bug was observed that caused * loops generated from such quantifiers to iterate 0 times. This originated * from a change in 79579fd5ee7cc3c120439b5d3187a09ffd5dcd6e. This model checks * that such a problem has not been reintroduced. */ var x: -1 .. 1; startstate begin x := 0; end; ruleset y: -1 .. 1 do rule begin x := y; end; end; rumur-2020.02.17/tests/read-undefined.m000066400000000000000000000002061362265074000174540ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test reading of an undefined value. var x: boolean; startstate begin end; rule begin x := !x; end; rumur-2020.02.17/tests/read-undefined2.m000066400000000000000000000002461362265074000175420ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test reading of an undefined record value. var x: record a: boolean; end; startstate begin end; rule begin x.a := !x.a; end; rumur-2020.02.17/tests/read-undefined3.m000066400000000000000000000002431362265074000175400ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test reading of an undefined array value. var x: array[0 .. 1] of boolean; startstate begin end; rule begin x[0] := !x[0]; end; rumur-2020.02.17/tests/recursion1.m000066400000000000000000000005731362265074000167030ustar00rootroot00000000000000-- test a recursive function definition var x: 0 .. 55; function fib(n: 0 .. 10): 0 .. 100; begin if n = 0 then return 0; elsif n = 1 then return 1; else return fib(n - 1) + fib(n - 2); end; end; startstate begin x := 0; end; rule x <= 10 ==> begin x := fib(x); end; rule begin x := 2; end; rule begin x := 5; end; rule begin x := 10; end; rumur-2020.02.17/tests/recursion2.m000066400000000000000000000005261362265074000167020ustar00rootroot00000000000000-- test a recursive procedure definition var x: 0 .. 55; procedure set(n: 0 .. 10); begin if n = 0 then x := 0; elsif n = 1 then x := 1; else set(n - 2); end; end; startstate begin x := 0; end; rule x <= 10 ==> begin set(x); end; rule begin x := 2; end; rule begin x := 5; end; rule begin x := 10; end; rumur-2020.02.17/tests/recursion3.m000066400000000000000000000006171362265074000167040ustar00rootroot00000000000000-- rumur_exit_code: 1 -- test that mutual recursion is rejected var x: boolean; function is_even(n: 0 .. 100): boolean; begin if n = 0 then return true; else return is_odd(n - 1); end; end; function is_odd(n: 0 .. 100): boolean; begin if n = 0 then return false; else return is_even(n - 1); end; end; startstate begin x := true; end; rule begin x := !x; end; rumur-2020.02.17/tests/recursion4.m000066400000000000000000000014301362265074000166770ustar00rootroot00000000000000-- This model tests a case of recursion where the recursive function has a prior -- call to a non-recursive function. This is interesting because at time of -- writing (commit 084489251754d9ccd9a23d7b3fba0989d338eb80) the recursive call -- to bar will be resolved to an incomplete, unresolved definition of bar. I.e. -- a version of bar in which the call to foo still has an unresolved target. It -- is suspected that this might be an error where a subtle bug could be provoked -- in future, so this test was introduced to guard against that. var x: boolean; function foo(x: boolean): boolean; begin return !x; end; function bar(x: boolean): boolean; begin foo(x); if x then bar(!x); end; return !x; end; startstate begin x := true; end; rule begin x := bar(x); end; rumur-2020.02.17/tests/recursion5.m000066400000000000000000000005551362265074000167070ustar00rootroot00000000000000-- a variant on recursion4.m where the recursive function is used as a guard var x: boolean; function foo(x: boolean): boolean; begin return !x; end; function bar(x: boolean): boolean; begin foo(x); if x then bar(!x); end; return !x; end; startstate begin x := true; end; rule begin x := bar(x); end; rule bar(x) ==> begin x := !x; end; rumur-2020.02.17/tests/reference-function-parameter.m000066400000000000000000000002221362265074000223370ustar00rootroot00000000000000var x: boolean; function foo(y: boolean): boolean; begin return y; end; startstate begin x := true; end; rule begin x := !foo(x); end; rumur-2020.02.17/tests/reference-function-parameter2.m000066400000000000000000000002261362265074000224250ustar00rootroot00000000000000var x: boolean; function foo(var y: boolean): boolean; begin return y; end; startstate begin x := true; end; rule begin x := !foo(x); end; rumur-2020.02.17/tests/reference-function-parameter3.m000066400000000000000000000002331362265074000224240ustar00rootroot00000000000000var x: boolean; function foo(var y: boolean): boolean; begin y := !y; return y; end; startstate begin x := true; end; rule begin foo(x); end; rumur-2020.02.17/tests/regression-bad-assumption-check.m000066400000000000000000000007071362265074000227670ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] /* There was a previous bug wherein assumption checks were done on the preceding * state of a rule, rather than on the final state. This model tests that we * have not re-introduced this bug. */ var x: boolean; startstate x := true; end; rule begin x := !x; end; rule begin x := x; end; -- This assumption should guarantee that the invariant below it never triggers. assume x invariant x rumur-2020.02.17/tests/return-expression-from-rule.m000066400000000000000000000003541362265074000222300ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Testing return with an expression within a rule. This is illegal and should -- fail. var x: boolean; startstate begin x := true; end; rule begin x := true; return 3; x := false; end; invariant x; rumur-2020.02.17/tests/return-from-rule.m000066400000000000000000000003311362265074000200260ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] -- Testing return statements within rules. var x: boolean; startstate begin x := true; end; rule begin x := true; return; x := false; end; invariant x; rumur-2020.02.17/tests/return-from-ruleset.m000066400000000000000000000004161362265074000205460ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] -- Testing return statements within rules within rulesets. var x: boolean; startstate begin x := true; end; ruleset y: boolean do rule begin x := true; return; x := false; end; end; invariant x; rumur-2020.02.17/tests/return-from-startstate.m000066400000000000000000000003371362265074000212630ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] -- Testing return statements within startstates. var x: boolean; startstate begin x := true; return; x := false; end; rule begin x := true; end; invariant x; rumur-2020.02.17/tests/rule-duplicate-name.m000066400000000000000000000004261362265074000204430ustar00rootroot00000000000000-- Test that duplicate rule names are accepted. Note that we define the two -- rules such that omitting either will cause a deadlock during checking. var x: boolean; startstate begin x := true; end; rule "foo" begin x := true; end; rule "foo" begin x := false; end; rumur-2020.02.17/tests/ruleset-invariant.m000066400000000000000000000004131362265074000202560ustar00rootroot00000000000000-- A model involving an invariant nested in a ruleset. This is relatively rare -- construct, but is compliant with the grammar. var x: boolean; startstate begin x := true; end; rule begin x := !x; end; ruleset y: boolean do invariant x = y | x = !y; end; rumur-2020.02.17/tests/ruleset-startstate.m000066400000000000000000000001601362265074000204600ustar00rootroot00000000000000var x: boolean; ruleset y: boolean do startstate begin x := y; end; end; rule begin x := !x; end; rumur-2020.02.17/tests/ruleset-trace.m000066400000000000000000000013651362265074000173700ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'^Rule\s+1\s*,\s*y\s*:\s*1\s*,\s*z\s*:\s*8\s*,\s*w\s*:\s*-32\s+fired\b', re.MULTILINE) /* As of v2019.01.12, Rumur printed counterexample traces involving rulesets * without showing the values of the parameters to the ruleset rules. This made * debugging such traces virtually impossible. This tests that we correctly * print a trace showing the values of such parameters. * * Github: issue #105 "missing parameters in ruleset trace" */ type t1: 0 .. 1; t2: 4 .. 10; t3: -44 .. -2 var x: boolean; startstate begin x := true; end; ruleset y: t1; z: t2; w: t3 do rule begin assert y != 1 | z != 8 | w != -32 "failed assertion"; x := !x; end; end; rumur-2020.02.17/tests/ruleset-trace2.m000066400000000000000000000015211362265074000174440ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'^Rule\s+2\s*,\s*y\s*:\s*1\s*,\s*z\s*:\s*8\s*,\s*w\s*:\s*-32\s+fired\b', re.MULTILINE) /* Similar to ruleset-trace.m, but with a benign first ruleset that ensures the * ruleset we're expecting to appear in the trace does not end up being index 1. * This is relevant with respect to how Rumur works internally, numbering rule * transitions via the rule_taken variable. */ type t1: 0 .. 1; t2: 4 .. 10; t3: -44 .. -2 var x: boolean; startstate begin x := true; end; -- an innocuous rule to make sure our problematic ruleset below will not get a -- trivial index ruleset y: t1; z: t2 do rule begin x := !x; end; end; ruleset y: t1; z: t2; w: t3 do rule begin assert y != 1 | z != 8 | w != -32 "failed assertion"; x := !x; end; end; rumur-2020.02.17/tests/ruleset-trace3.m000066400000000000000000000010201362265074000174370ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'^Startstate\s+1\s*,\s*y\s*:\s*1\s*,\s*z\s*:\s*8\s*,\s*w\s*:\s*-32\s+fired\b', re.MULTILINE) /* This is a similar test to ruleset-trace.m but using a ruleset-contained * startstate, instead of an ordinary rule. */ type t1: 0 .. 1; t2: 4 .. 10; t3: -44 .. -2 var x: boolean; ruleset y: t1; z: t2; w: t3 do startstate begin assert y != 1 | z != 8 | w != -32 "failed assertion"; x := true; end; end; rule begin x := !x; end; rumur-2020.02.17/tests/rumur-run-model.py000077500000000000000000000023351362265074000200600ustar00rootroot00000000000000#!/usr/bin/env python3 'test that rumur-run can check a basic model' import os import platform import subprocess as sp import sys if platform.system() == 'Linux' and 'TRAVIS' in os.environ: release = sp.check_output(['lsb_release', '--release', '--short'], universal_newlines=True).strip() if release == '14.04': if os.environ.get('CC') in ('gcc-7', 'gcc-8', 'gcc-9'): # XXX: When using GCC ≥7 on Ubuntu 14.04 (as we do in CI) and the compiler # is passed -march=native -mtune=native, it generates assembly sequences # that GAS from Binutils 2.24 (the default on this platform) is unable to # comprehend. It is possible to install Binutils 2.26 but in Travis it # seems flaky as to whether this works. To avoid false failures, just skip # this test in that configuration. print('this test is flaky on Ubuntu 14.04 with GCC 7+') sys.exit(125) RUMUR_RUN = os.path.join(os.path.dirname(__file__), '../rumur/src/rumur-run') MODEL = ''' var x: boolean; startstate begin x := true; end; rule begin x := !x; end; ''' def main(): p = sp.run(['python3', RUMUR_RUN], input=MODEL.encode('utf-8', 'replace')) return p.returncode if __name__ == '__main__': sys.exit(main()) rumur-2020.02.17/tests/rumur-run-version.py000077500000000000000000000004751362265074000204500ustar00rootroot00000000000000#!/usr/bin/env python3 'basic test that rumur-run can execute successfully' import os import subprocess import sys RUMUR_RUN = os.path.join(os.path.dirname(__file__), '../rumur/src/rumur-run') def main(): subprocess.check_call(['python3', RUMUR_RUN, '--version']) if __name__ == '__main__': sys.exit(main()) rumur-2020.02.17/tests/run-tests.py000077500000000000000000000317711362265074000167600ustar00rootroot00000000000000#!/usr/bin/env python3 import abc import argparse import codecs import enum import itertools import multiprocessing import os import platform import re import shutil import subprocess as sp import sys import tempfile from typing import Optional, Tuple CPUS = multiprocessing.cpu_count() STDOUT_ISATTY = os.isatty(sys.stdout.fileno()) GREEN = '\033[32m' if STDOUT_ISATTY else '' RED = '\033[31m' if STDOUT_ISATTY else '' YELLOW = '\033[33m' if STDOUT_ISATTY else '' RESET = '\033[0m' if STDOUT_ISATTY else '' def enc(s): return s.encode('utf-8', 'replace') def dec(s): return s.decode('utf-8', 'replace') # let the user define a range of tests to run try: MIN_TEST = int(os.environ['MIN_TEST']) except: MIN_TEST = None try: MAX_TEST = int(os.environ['MAX_TEST']) except: MAX_TEST = None def in_range(index: int) -> bool: if MIN_TEST is not None and index < MIN_TEST: return False if MAX_TEST is not None and index > MAX_TEST: return False return True print_lock = multiprocessing.Lock() def pr(s: str) -> None: print_lock.acquire() sys.stdout.write(s) sys.stdout.flush() print_lock.release() def which(cmd: str) -> Optional[str]: try: return sp.check_output(['which', cmd], stderr=sp.DEVNULL, universal_newlines=True).strip() except sp.CalledProcessError: return None def run(args: [str], stdin: Optional[str] = None) -> Tuple[int, str, str]: if stdin is not None: stdin = enc(stdin) p = sp.run(args, stdout=sp.PIPE, stderr=sp.PIPE, input=stdin) return p.returncode, dec(p.stdout), dec(p.stderr) # C compiler CC = os.environ.get('CC', which('cc')) def has_mcx16() -> bool: 'does the compiler support -mcx16?' code = 'int main(void) {\n' \ ' return 0;\n' \ '}\n' args = [CC, '-x', 'c', '-std=c11', '-mcx16', '-', '-o', os.devnull] ret, _, _ = run(args, code) return ret == 0 HAS_MCX16 = has_mcx16() # initial flags to pass to our C compiler C_FLAGS = ['-x', 'c', '-std=c11', '-Werror=format', '-Werror=sign-compare', '-Werror=type-limits'] + (['-mcx16'] if HAS_MCX16 else []) def needs_libatomic() -> bool: 'does the toolchain need -latomic to support dword CAS?' code = '#include \n' \ '\n' \ 'int main(void) {\n' \ '#if __SIZEOF_POINTER__ <= 4\n' \ ' uint64_t target = 0;\n' \ '#elif __SIZEOF_POINTER__ <= 8\n' \ ' unsigned __int128 target = 0;\n' \ '#endif\n' \ ' return (int)__sync_val_compare_and_swap(&target, 0, 1);\n' \ '}\n' args = [CC, '-x', 'c', '-std=c11', '-', '-o', os.devnull] if HAS_MCX16: args.append('-mcx16') ret, _, _ = run(args, code) return ret != 0 NEEDS_LIBATOMIC = needs_libatomic() VERIFIER_RNG = os.path.abspath(os.path.join(os.path.dirname(__file__), '../misc/verifier.rng')) MURPHI2XML_RNG = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'misc', 'murphi2xml.rng')) SECCOMP_SUPPORTED = os.path.abspath(os.path.join(os.path.dirname(__file__), '../misc/seccomp-supported.sh')) def has_sandbox() -> bool: 'whether the current platform has sandboxing support for the verifier' if platform.system() == 'Darwin': return True if platform.system() == 'FreeBSD': return True if platform.system() == 'Linux': ret, _, _ = run([SECCOMP_SUPPORTED]) return ret == 0 if platform.system() == 'OpenBSD': return True return False HAS_SANDBOX = has_sandbox() def smt_args(bv: bool = False) -> [str]: 'get SMT arguments for an available solver' # preference 1: Z3 if which('z3') is not None: # we leave a blank logic here, as Z3 performs best when not given a logic args = ['--smt-path', 'z3', '--smt-arg=-smt2', '--smt-arg=-in'] if bv: args += ['--smt-bitvectors', 'on'] return args # preference 2: CVC4 if which('cvc4') is not None: args = ['--smt-path', 'cvc4', '--smt-arg=--lang=smt2', '--smt-arg=--rewrite-divk'] if bv: args += ['--smt-prelude', '(set-logic AUFBV)', '--smt-bitvectors', 'on'] else: args += ['--smt-prelude', '(set-logic AUFLIA)'] return args # otherwise, give up return [] SMT_ARGS = smt_args() SMT_BV_ARGS = smt_args(True) class TemporaryDirectory(object): 'an mkdtemp() that cleans up after itself' def __init__(self): self.tmp = None def __enter__(self) -> str: self.tmp = tempfile.mkdtemp() return self.tmp def __exit__(self, *_): if self.tmp is not None: shutil.rmtree(self.tmp) HAS_VALGRIND = which('valgrind') is not None class Result(abc.ABC): pass class Skip(Result): def __init__(self, reason: str): self.reason = reason class Fail(Result): def __init__(self, output: str): self.output = output class Test(abc.ABC): @abc.abstractmethod def description(self) -> str: raise NotImplementedError @abc.abstractmethod def run(self) -> Optional[Result]: raise NotImplementedError # recogniser for a '-- rumur_flags: ...' etc line TWEAK_LINE = re.compile(r'\s*--\s*(?P[a-zA-Z_]\w*)\s*:(?P.*)$') class Tweakable(Test): 'a test case that can take extra customisation via comment lines' def __init__(self): # default options self.rumur_flags = [] self.rumur_exit_code = 0 self.checker_exit_code = 0 self.checker_output = None self.skip_reason = None def apply_options(self, model: str) -> None: 'check for special lines at the start of current model overriding defaults' with open(model, 'rt', encoding='utf-8') as f: for line in f: m = TWEAK_LINE.match(line) if m is None: break key = m.group('key') value = m.group('value').strip() setattr(self, key, eval(value)) class Model(Tweakable): def __init__(self, model: str, debug: bool, optimised: bool, \ multithreaded: bool, xml: bool): super().__init__() self.model = model self.debug = debug self.optimised = optimised self.multithreaded = multithreaded self.xml = xml self.valgrind = HAS_VALGRIND def description(self) -> str: return f'{"D" if self.debug else " "}' \ f'{"O" if self.optimised else " "}' \ f'{"M" if self.multithreaded else " "}' \ f'{"X" if self.xml else " "}' \ f'{"V" if HAS_VALGRIND else " "}' \ f' {os.path.basename(self.model)}' def run(self) -> Result: self.apply_options(self.model) if self.skip_reason is not None: return Skip(self.skip_reason) # build up arguments to call rumur args = ['rumur', '--output', '/dev/stdout', self.model] \ + (['--debug'] if self.debug else []) \ + (['--output-format', 'machine-readable'] if self.xml else []) \ + (['--threads', '2'] if self.multithreaded and CPUS == 1 else []) \ + (['--threads', '1'] if not self.multithreaded else []) \ + self.rumur_flags if HAS_VALGRIND: args = ['valgrind', '--leak-check=full', '--show-leak-kinds=all', '--error-exitcode=42'] + args # call rumur ret, stdout, stderr = run(args) if HAS_VALGRIND: if ret == 42: return Fail(f'Memory leak:\n{stdout}{stderr}') if ret != self.rumur_exit_code: return Fail(f'Rumur failed:\n{stdout}{stderr}') # if we expected to fail, we are done if ret != 0: return model_c = stdout with TemporaryDirectory() as tmp: # build up arguments to call the C compiler model_bin = os.path.join(tmp, 'model.exe') args = [CC] + C_FLAGS + ['-o', model_bin, '-', '-lpthread'] if NEEDS_LIBATOMIC: args.append('-latomic') # call the C compiler ret, stdout, stderr = run(args, model_c) if ret != 0: return Fail(f'C compilation failed:\n{stdout}{stderr}') # now run the model itself ret, stdout, stderr = run([model_bin]) if ret != self.checker_exit_code: return Fail(f'Unexpected checker exit status {ret}:\n{stdout}{stderr}') # if the test has a stdout expectation, check that now if self.checker_output is not None: if self.checker_output.search(stdout) is None: return Fail( 'Checker output did not match expectation regex:\n' f'{stdout}{stderr}') # coarse grained check for whether the model contains a 'put' statement that # could screw up XML validation with open(self.model, 'rt', encoding='utf-8') as f: has_put = re.search(r'\bput\b', f.read()) is not None if self.xml and not has_put: model_xml = stdout if which('xmllint') is None: return Skip('xmllint not available') # validate the XML args = ['xmllint', '--relaxng', VERIFIER_RNG, '--noout', '-'] ret, stdout, stderr = run(args, model_xml) if ret != 0: return Fail( 'Failed to XML-validate machine reachable output:\n' f'{stdout}{stderr}') class Executable(Test): def __init__(self, exe: str): self.exe = exe def description(self) -> str: return f'----- exec {os.path.basename(self.exe)}' def run(self) -> Result: ret, stdout, stderr = run(self.exe) output = f'{stdout}{stderr}' return None if ret == 0 else \ Skip(output.strip()) if ret == 125 else \ Fail(output) class Murphi2XMLTest(Tweakable): def __init__(self, model: str): super().__init__() self.model = model self.xml = False # dummy setting that tests might reference def description(self) -> str: return f'----{"V" if HAS_VALGRIND else " "} ' \ f'murphi2xml {os.path.basename(self.model)}' def run(self) -> Result: self.apply_options(self.model) args = ['murphi2xml', self.model] if HAS_VALGRIND: args = ['valgrind', '--leak-check=full', '--show-leak-kinds=all', '--error-exitcode=42'] + args ret, stdout, stderr = run(args) if HAS_VALGRIND: if ret == 42: return Fail(f'Memory leak:\n{stdout}{stderr}') # if rumur was expected to reject this model, we allow murphi2xml to fail if self.rumur_exit_code == 0 and ret != 0: return Fail(f'Unexpected murphi2xml exit status {ret}:\n{stdout}{stderr}') if ret != 0: return None # murphi2xml will have written XML to its stdout xmlcontent = stdout # See if we have xmllint if which('xmllint') is None: return Skip('xmllint not available for validation') # Validate the XML ret, stdout, stderr = run(['xmllint', '--relaxng', MURPHI2XML_RNG, '--noout', '-'], xmlcontent) if ret != 0: return Fail(f'Failed to validate:\n{stdout}{stderr}') def check(test: Test) -> int: 'run a test case and report its result' result = test.run() if result is None: pr(f'{GREEN}PASS{RESET} {test.description()}\n') return 1, 0, 0 elif isinstance(result, Skip): pr(f'{YELLOW}SKIP{RESET} {test.description()} [{result.reason}]\n') return 0, 1, 0 else: assert isinstance(result, Fail) pr(f'{RED}FAIL{RESET} {test.description()}\n{result.output}') return 0, 0, 1 def main(args: [str]) -> int: # setup stdout to make encoding errors non-fatal sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'replace') parser = argparse.ArgumentParser(description='Rumur test suite') parser.add_argument('--jobs', '-j', type=int, help='number of threads to use', default=CPUS) parser.add_argument('testcase', nargs='*', help='specific test case(s) to run') options = parser.parse_args(args[1:]) pool = multiprocessing.Pool(options.jobs) index = 1 tests = [] # find files in our directory root = os.path.dirname(__file__) for stem in sorted(os.listdir(root)): path = os.path.join(root, stem) # skip if this does not match the user’s filter if len(options.testcase) > 0 and stem not in options.testcase: continue # skip directories if os.path.isdir(path): continue # skip ourselves if path == __file__: continue # if this is executable, treat it as a test case if os.access(path, os.X_OK): if in_range(index): tests.append(Executable(path)) index += 1 # if this is not a model, skip the remaining generic logic if os.path.splitext(path)[-1] != '.m': continue for debug, optimised, multithreaded, xml \ in itertools.product((False, True), repeat=4): # debug output causes invalid XML, so skip if debug and xml: continue if in_range(index): tests.append(Model(path, debug, optimised, multithreaded, xml)) index += 1 if in_range(index): tests.append(Murphi2XMLTest(path)) index += 1 if len(tests) == 0: pr(f'no tests found\n') return -1 pr(f'Running {len(tests)} tests using {options.jobs} threads...\n' ' +------ debug\n' ' |+----- optimised\n' ' ||+---- multithreaded\n' ' |||+--- XML\n' ' ||||+-- Valgrind\n') # run all tests in parallel and accumulate the results passed, skipped, failed = map(sum, zip(*pool.imap_unordered(check, tests))) pr(f'{passed} passed, {skipped} skipped, {failed} failed ' f'out of {len(tests)} total tests\n') return 0 if failed == 0 else 1 if __name__ == '__main__': sys.exit(main(sys.argv)) rumur-2020.02.17/tests/scalarset-trivial.m000066400000000000000000000004351362265074000202370ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] -- There's no utility to the scalarset in this model. It's purely to validate -- that a scalarset can exist. type t: scalarset(2); var x: t; ruleset i: t do startstate begin x := i; end; end; rule begin x := x; end; rumur-2020.02.17/tests/simple-deadlock.m000066400000000000000000000001641362265074000176420ustar00rootroot00000000000000-- checker_exit_code: 1 var x: boolean; startstate begin x := true; end; rule x ==> begin x := false; end; rumur-2020.02.17/tests/smart-quotes.m000066400000000000000000000006431362265074000172530ustar00rootroot00000000000000-- checker_exit_code: 1 -- checker_output: None if self.xml else re.compile(r'^Rule "foo" fired', re.MULTILINE) -- Test that smart quotes can be used to delimit a string. This test case works -- by deliberately triggering a model error that should cause a backtrace -- containing a correctly parsed version of the rule name. var x: boolean startstate begin x := true; end rule “foo” begin x := false; end rumur-2020.02.17/tests/smt-add.m000066400000000000000000000006151362265074000161370ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with addition var x: 0 .. 1; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x + 1 = 1 | x + 1 = 2 then y := !y; end; end; rumur-2020.02.17/tests/smt-array-bool-index.m000066400000000000000000000010341362265074000205570ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None /* test whether the SMT bridge can simplify expressions involving arrays with * boolean indicies */ type t: array[boolean] of 0 .. 1; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[true] = x[true] then y := !y; end; end; rumur-2020.02.17/tests/smt-array-bool-value-and-index.m000066400000000000000000000010451362265074000224330ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None /* test whether the SMT bridge can simplify expressions involving arrays with * bool values and indicies */ type t: array[boolean] of boolean; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[true] = x[true] then y := !y; end; end; rumur-2020.02.17/tests/smt-array-bool-value.m000066400000000000000000000010241362265074000205630ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None /* test whether the SMT bridge can simplify expressions involving arrays with * boolean values */ type t: array[0 .. 1] of boolean; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0] = x[0] then y := !y; end; end; rumur-2020.02.17/tests/smt-array-enum-index.m000066400000000000000000000010311362265074000205650ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None /* test whether the SMT bridge can simplify expressions involving arrays with * enum indicies */ type t: array[enum { A, B }] of 0 .. 1; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[A] = x[A] then y := !y; end; end; rumur-2020.02.17/tests/smt-array-enum-index2.m000066400000000000000000000010021362265074000206450ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- variant of smt-array-enum-index.m that pre-defines the enum type type e: enum { A, B }; t: array[e] of 0 .. 1; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[A] = x[A] then y := !y; end; end; rumur-2020.02.17/tests/smt-array-enum-value-and-index.m000066400000000000000000000010531362265074000224430ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None /* test whether the SMT bridge can simplify expressions involving arrays with * enum values and indicies */ type t: array[enum { C, D }] of enum { A, B }; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[C] = x[C] then y := !y; end; end; rumur-2020.02.17/tests/smt-array-enum-value-and-index2.m000066400000000000000000000010341362265074000225240ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- variant of smt-array-enum-value-and-index.m which pre-defines the enums type e1: enum { C, D }; e2: enum { A, B }; t: array[e1] of e2; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[C] = x[C] then y := !y; end; end; rumur-2020.02.17/tests/smt-array-enum-value.m000066400000000000000000000010271362265074000205770ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None /* test whether the SMT bridge can simplify expressions involving arrays with * enum values */ type t: array[0 .. 1] of enum { A, B }; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0] = x[0] then y := !y; end; end; rumur-2020.02.17/tests/smt-array-enum-value2.m000066400000000000000000000007751362265074000206720ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- variant of smt-array-enum-value.m that pre-defines the enum type e: enum { A, B }; t: array[0 .. 1] of e; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0] = x[0] then y := !y; end; end; rumur-2020.02.17/tests/smt-array-of-record.m000066400000000000000000000007171362265074000204060ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test the SMT bridge can cope with arrays of records var x: array[0 .. 1] of record x: 0 .. 1; end; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge is working correctly, it will simplify the following -- expression to 'true' avoiding the read of an undefined variable if x[0].x = x[0].x then y := !y; end; end; rumur-2020.02.17/tests/smt-array-range.m000066400000000000000000000007701362265074000176210ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test whether the SMT bridge can simplify expressions involving arrays type t: array[0 .. 1] of 0 .. 1; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0] = x[0] then y := !y; end; end; rumur-2020.02.17/tests/smt-array-range2.m000066400000000000000000000007611362265074000177030ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- slight variant on smt-array-range.m with a pre-defined type type r: 0 .. 1; t: array[r] of r; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0] = x[0] then y := !y; end; end; rumur-2020.02.17/tests/smt-bool-literal.m000066400000000000000000000013701362265074000177730ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None /* This model attempts to provoke a bug first observed on commit * 5d4f1939ddc5d5d9336f0ce35e953c51e8b5aeca. The SMT bridge did not correctly * understand the boolean literals and would form malformed problems when seeing * them. If this problem has been introduced, this model will fail verification * by reading an undefined variable. */ var x: boolean; y: boolean; startstate begin x := true; end; rule begin /* if the SMT bridge correctly deals with boolean literals, the following * expression should be simplified to true and avoid the access to an * undefined variable */ if y = true | y = false then x := !x; end; end; rumur-2020.02.17/tests/smt-bv-add.m000066400000000000000000000006601362265074000165440ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with addition when using a bitvector logic var x: 0 .. 1; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x + 1 = 1 | x + 1 = 2 then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-div.m000066400000000000000000000006601362265074000165760ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with division when using a bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x / 2 = 2 | x / 2 = 3 then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-div2.m000066400000000000000000000006741362265074000166650ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with division by non-constant when using a -- bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if 2 * x / x = 2 then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-exists.m000066400000000000000000000007241362265074000173340ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- equivalent of smt-exists.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z: 1 .. 2 do z = 1 end then x := !x; end; end; rumur-2020.02.17/tests/smt-bv-exists2.m000066400000000000000000000007431362265074000174170ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- equivalent of smt-exists2.m but using --smt-bitvectors on type t: 1 .. 2; var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z: t do z = 1 end then x := !x; end; end; rumur-2020.02.17/tests/smt-bv-exists3.m000066400000000000000000000007271362265074000174220ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- equivalent of smt-exists3.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z := 1 to 2 do z = 1 end then x := !x; end; end; rumur-2020.02.17/tests/smt-bv-exists4.m000066400000000000000000000007351362265074000174220ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- equivalent of smt-exists4.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | !exists z := 2 to 4 by 2 do z = 3 end then x := !x; end; end; rumur-2020.02.17/tests/smt-bv-forall.m000066400000000000000000000007341362265074000172750ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- equivalent of smt-forall.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z: 1 .. 2 do z = 1 | z = 2 end then x := !x; end; end; rumur-2020.02.17/tests/smt-bv-forall2.m000066400000000000000000000007531362265074000173600ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- equivalent of smt-forall2.m but using --smt-bitvectors on type t: 1 .. 2; var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z: t do z = 1 | z = 2 end then x := !x; end; end; rumur-2020.02.17/tests/smt-bv-forall3.m000066400000000000000000000007371362265074000173630ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- equivalent of smt-forall3.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z := 1 to 2 do z = 1 | z = 2 end then x := !x; end; end; rumur-2020.02.17/tests/smt-bv-forall4.m000066400000000000000000000007441362265074000173620ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- equivalent of smt-forall4.m but using --smt-bitvectors on var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z := 2 to 4 by 2 do z = 2 | z = 4 end then x := !x; end; end; rumur-2020.02.17/tests/smt-bv-geq.m000066400000000000000000000006441362265074000165720ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with >= when using a bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x >= 4 & 6 >= x then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-gt.m000066400000000000000000000006411362265074000164250ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with > when using a bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x > 3 & 7 > x then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-leq.m000066400000000000000000000006441362265074000165770ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with <= when using a bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if 4 <= x & x <= 6 then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-lt.m000066400000000000000000000006421362265074000164330ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with <= when using a bitvector logic var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if 3 < x & x < 7 then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-mod.m000066400000000000000000000006561362265074000166000ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with modulo when using a bitvector logic var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x % 5 = 3 | x % 5 = 4 then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-mod2.m000066400000000000000000000006671362265074000166640ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with modulo by a non-constant when using -- a bitvector logic var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x % x = 0 then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-mul.m000066400000000000000000000006701362265074000166120ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with multiplication when using a bitvector logic var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x * 5 = 40 | x * 5 = 45 then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-neg.m000066400000000000000000000006541362265074000165700ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with negation when using a bitvector logic var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if -x = -8 | -x = -9 then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-negative-literal.m000066400000000000000000000004661362265074000212540ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- similar to smt-negative-literal, but for --smt-bitvectors on const N: -10; var x: boolean; y: boolean; startstate begin y := true; end; rule begin if x | !x then y := !y; end; end; rumur-2020.02.17/tests/smt-bv-sub.m000066400000000000000000000006631362265074000166100ustar00rootroot00000000000000-- rumur_flags: SMT_BV_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_BV_ARGS) == 0 else None -- test that the SMT bridge can cope with subtraction when using a bitvector logic var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x - 1 = 7 | x - 1 = 8 then y := !y; end; end; rumur-2020.02.17/tests/smt-const.m000066400000000000000000000010171362265074000165320ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None /* test that the SMT bridge is capable of dealing with formulas involving * integer constants */ const C: 2; var x: 2 .. 2; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with constants, it should judge this condition -- to be tautological and turn it into just `true`, avoiding an error from -- reading x while it is undefined if x = C then y := !y end; end; rumur-2020.02.17/tests/smt-div.m000066400000000000000000000006151362265074000161710ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with division var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x / 2 = 2 | x / 2 = 3 then y := !y; end; end; rumur-2020.02.17/tests/smt-enum-typeexprid.m000066400000000000000000000006541362265074000205510ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- a version of smt-enum.m using a typedef type t: enum { A }; var x: t; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with enums, it should turn the following -- condition into `true`, avoiding a read of an undefined value from x if x = A then y := !y; end; end; rumur-2020.02.17/tests/smt-enum.m000066400000000000000000000006601362265074000163530ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge is capable of dealing with enums var x: enum { A }; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with enums, it should turn the following -- condition into `true`, avoiding a read of an undefined value from x if x = A then y := !y; end; end; rumur-2020.02.17/tests/smt-enum2-typeexprid.m000066400000000000000000000006701362265074000206310ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- a version of smt-enum2.m using a typedef type t: enum { A, B }; var x: t; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with enums, it should turn the following -- condition into `true`, avoiding a read of an undefined value from x if x = A | x = B then y := !y; end; end; rumur-2020.02.17/tests/smt-enum2-typeexprid2.m000066400000000000000000000007171362265074000207150ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- a double indirected version of smt-enum2-typeexprid.m type t: enum { A, B }; t2: t; var x: t2; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with enums, it should turn the following -- condition into `true`, avoiding a read of an undefined value from x if x = A | x = B then y := !y; end; end; rumur-2020.02.17/tests/smt-enum2.m000066400000000000000000000006561362265074000164420ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- slightly more involved version of smt-enum.m var x: enum { A, B }; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with enums, it should turn the following -- condition into `true`, avoiding a read of an undefined value from x if x = A | x = B then y := !y; end; end; rumur-2020.02.17/tests/smt-exists.m000066400000000000000000000007171362265074000167310ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can deal with exists expressions var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z: 1 .. 2 do z = 1 end then x := !x; end; end; rumur-2020.02.17/tests/smt-exists2.m000066400000000000000000000007271362265074000170140ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- similar to smt-exists, but using a pre-defined type type t: 1 .. 2; var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z: t do z = 1 end then x := !x; end; end; rumur-2020.02.17/tests/smt-exists3.m000066400000000000000000000007071362265074000170130ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- a variant of smt-exists.m using an inline range var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | exists z := 1 to 2 do z = 1 end then x := !x; end; end; rumur-2020.02.17/tests/smt-exists4.m000066400000000000000000000007331362265074000170130ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- a variant of smt-exists.m using an inline range with stepping var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | !exists z := 2 to 4 by 2 do z = 3 end then x := !x; end; end; rumur-2020.02.17/tests/smt-forall.m000066400000000000000000000007271362265074000166720ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can deal with forall expressions var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z: 1 .. 2 do z = 1 | z = 2 end then x := !x; end; end; rumur-2020.02.17/tests/smt-forall2.m000066400000000000000000000007371362265074000167550ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- similar to smt-forall, but using a pre-defined type type t: 1 .. 2; var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z: t do z = 1 | z = 2 end then x := !x; end; end; rumur-2020.02.17/tests/smt-forall3.m000066400000000000000000000007171362265074000167540ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- a variant of smt-forall.m using an inline range var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z := 1 to 2 do z = 1 | z = 2 end then x := !x; end; end; rumur-2020.02.17/tests/smt-forall4.m000066400000000000000000000007421362265074000167530ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- a variant of smt-forall.m using an inline range with stepping var x: boolean; y: boolean; startstate begin x := true; end; rule begin -- if the SMT bridge is working correctly, it should simplify the condition as -- a tautology into true, avoiding the read of an undefined variable if y | forall z := 2 to 4 by 2 do z = 2 | z = 4 end then x := !x; end; end; rumur-2020.02.17/tests/smt-geq.m000066400000000000000000000006011362265074000161560ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with >= var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x >= 4 & 6 >= x then y := !y; end; end; rumur-2020.02.17/tests/smt-gt.m000066400000000000000000000005761362265074000160270ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with > var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x > 3 & 7 > x then y := !y; end; end; rumur-2020.02.17/tests/smt-leq.m000066400000000000000000000006011362265074000161630ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with <= var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x <= 6 & 4 <= x then y := !y; end; end; rumur-2020.02.17/tests/smt-lt.m000066400000000000000000000005771362265074000160350ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with <= var x: 4 .. 6; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x < 7 & 3 < x then y := !y; end; end; rumur-2020.02.17/tests/smt-mod.m000066400000000000000000000006131362265074000161640ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with modulo var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x % 5 = 3 | x % 5 = 4 then y := !y; end; end; rumur-2020.02.17/tests/smt-mul.m000066400000000000000000000006251362265074000162050ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with multiplication var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x * 5 = 40 | x * 5 = 45 then y := !y; end; end; rumur-2020.02.17/tests/smt-neg.m000066400000000000000000000006111362265074000161540ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with negation var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if -x = -8 | -x = -9 then y := !y; end; end; rumur-2020.02.17/tests/smt-negative-literal.m000066400000000000000000000012721362265074000206430ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- As observed on commit 852de6485322fe6e0dfaa8efa0109f23f634bf3f, models with -- negative literal values would cause malformed SMT problems to be constructed. -- This model tests that this bug has not been reintroduced. Note that the -- negative literal does not actually need to be used in the model for the bug -- to manifest. const N: -10; var x: boolean; y: boolean; startstate begin y := true; end; rule begin -- the SMT bridge should be able to optimise the following conditional into -- `true` preventing read of an undefined value if x | !x then y := !y; end; end; rumur-2020.02.17/tests/smt-nested-array.m000066400000000000000000000010261362265074000200020ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test whether the SMT bridge can simplify expressions involving nested arrays type t: array[0 .. 1] of array[0 .. 1] of 0 .. 1; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x[0][0] = x[0][0] then y := !y; end; end; rumur-2020.02.17/tests/smt-range.m000066400000000000000000000006621362265074000165050ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can deal with integer range types var x: 2 .. 10; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge can deal with integer ranges, it should turn the -- following condition into `true`, avoiding a read of x which is undefined if x > 1 then y := !y; end; end; rumur-2020.02.17/tests/smt-record-bool-field.m000066400000000000000000000010231362265074000206710ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test whether the SMT bridge can simplify expressions involving records with -- boolean fields type t: record x: boolean; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = x.x then y := !y; end; end; rumur-2020.02.17/tests/smt-record-bool-field2.m000066400000000000000000000007471362265074000207670ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- variant on smt-record-bool-field.m type t: record x: boolean; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = true | x.x = false then y := !y; end; end; rumur-2020.02.17/tests/smt-record-enum-field.m000066400000000000000000000010271362265074000207060ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test whether the SMT bridge can simplify expressions involving records with -- enum members type t: record x: enum { A, B }; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = x.x then y := !y; end; end; rumur-2020.02.17/tests/smt-record-enum-field2.m000066400000000000000000000010051362265074000207640ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- variant of smt-record-enum-field.m that pre-defines the enum type type e: enum { A, B }; t: record x: e; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = x.x then y := !y; end; end; rumur-2020.02.17/tests/smt-record-of-array.m000066400000000000000000000007171362265074000204060ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test the SMT bridge can cope with records of arrays var x: record x: array[0 .. 1] of 0 .. 1; end; y: boolean; startstate begin y := true; end; rule begin -- if the SMT bridge is working correctly, it will simplify the following -- expression to 'true' avoiding the read of an undefined variable if x.x[0] = x.x[0] then y := !y; end; end; rumur-2020.02.17/tests/smt-record-range-field.m000066400000000000000000000010211362265074000210300ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test whether the SMT bridge can simplify expressions involving records with -- range fieldss type t: record x: 0 .. 1; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = x.x then y := !y; end; end; rumur-2020.02.17/tests/smt-record-range-field2.m000066400000000000000000000007771362265074000211330ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- slight variant on smt-record-range-field.m with a pre-defined type type r: 0 .. 1; t: record x: r; end; var x: t; y: boolean; startstate begin y := true; end; rule begin /* if the SMT bridge is working correctly, it will simplify the condition in * the following expression to `true` avoiding the read of an undefined * variable */ if x.x = x.x then y := !y; end; end; rumur-2020.02.17/tests/smt-shadowed-decl.m000066400000000000000000000013111362265074000201040ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- This model tests a scenario where one declaration shadows another during -- translation across the SMT bridge. If everything works correctly, Rumur -- should form a valid SMT problem and the SMT solver should find that the -- condition in the if statement is a tautology and reduce it to "true", -- removing the read of an undefined variable. However, on commit -- 4ff47d10ee40a4947c3ee0463fddbf6f0fee1857 it was observed that this generates -- an invalid SMT problem. var x: boolean; y: boolean; startstate begin y := true; end; rule var x: boolean; begin if x | !x then y := !y; end; end; rumur-2020.02.17/tests/smt-simplify.m000066400000000000000000000007701362265074000172450ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None /* This model tests whether Rumur is capable of simplifying simple conditions at * code generation time. If it is, then it will replace the `y = y` check with * true and this model will pass. If not, the check will remain and cause a read * of an undefined value at runtime. */ var x: boolean; y: boolean; startstate begin x := true; end; rule begin if y = y then x := !x; end; end; rumur-2020.02.17/tests/smt-sub.m000066400000000000000000000006201362265074000161740ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- test that the SMT bridge can cope with subtraction var x: 8 .. 9; y: boolean; startstate begin y := true; end; rule begin -- the following condition should be simplified to `true` avoiding a read of -- `x` when it is undefined if x - 1 = 7 | x - 1 = 8 then y := !y; end; end; rumur-2020.02.17/tests/smt-typedecl-boolean.m000066400000000000000000000013421362265074000206330ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- This model is designed to provoke a problem first observed on -- 787f074328874a470d595576ae9e8b16837582f4, where the SMT bridge would -- incorrectly mangle 'boolean' as part of a TypeDecl. This would lead to SMT -- problems including the undefined symbol 'ru_boolean'. type bit: boolean; var x: bit; y: bit; startstate begin x := true; end; rule begin -- if the SMT bridge correctly understands the problem passed to it, it should -- detect the following as a tautology causing simplification to collapse it -- to 'true' avoid the read of an undefined variable if y = true | y = false then x := !x; end; end; rumur-2020.02.17/tests/smt-typeexprid.m000066400000000000000000000004351362265074000176040ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- similar to smt-range.m but using a named type type r: 2 .. 10; var x: r; y: boolean; startstate begin y := true; end; rule begin if x > 1 then y := !y; end; end; rumur-2020.02.17/tests/smt-typeexprid2.m000066400000000000000000000004471362265074000176710ustar00rootroot00000000000000-- rumur_flags: SMT_ARGS -- skip_reason: 'no SMT solver available' if len(SMT_ARGS) == 0 else None -- a double indirected version of smt-typeexprid.m type r: 2 .. 10; s: r; var x: s; y: boolean; startstate begin y := true; end; rule begin if x > 1 then y := !y; end; end; rumur-2020.02.17/tests/strace-sandbox.sh000077500000000000000000000042211362265074000177010ustar00rootroot00000000000000#!/usr/bin/env bash # run a sandboxed checker under strace # # When the Linux seccomp sandbox causes the checker to terminate because it made # an unauthorised system call, it is difficult to debug what happened without # stracing the process to see the denied system call. This test case automates # this work flow. If the checker runs fine with the sandbox enabled, this test # case is irrelevant. However, if the basic-sandbox.m test fails, this test will # hopefully automatically diagnose the failure. The purpose of this existing # within the test suite itself is to debug failures that occur within a CI # environment you do not have access to or cannot easily replicate, like the # Debian auto builders. # check we have strace if ! which strace &>/dev/null; then printf 'strace not available\n' exit 125 fi if [ "$(uname -s)" = "Linux" ]; then MY_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd) # check whether seccomp is available if ! ${MY_DIR}/../misc/seccomp-supported.sh; then print 'seccomp sandboxing not supported\n' exit 125 fi fi # echo commands set -x # create a temporary space to work in TMP=$(mktemp -d) pushd ${TMP} # create a basic model cat - >model.m <mcx16-check.c <libatomic-check.c < int main(void) { #if __SIZEOF_POINTER__ <= 4 uint64_t target = 0; #elif __SIZEOF_POINTER__ <= 8 unsigned __int128 target = 0; #endif return (int)__sync_val_compare_and_swap(&target, 0, 1); } EOT if ${CC:-cc} -std=c11 ${MCX16} libatomic-check.c -o /dev/null; then LIBATOMIC= else LIBATOMIC=-latomic fi # compile the sandboxed checker ${CC:-cc} -std=c11 ${MCX16} model.c -o model.exe ${LIBATOMIC} -lpthread # run the model under strace strace ./model.exe RET=$? # clean up popd rm -rf ${TMP} exit ${RET} rumur-2020.02.17/tests/string-escape1.m000066400000000000000000000002611362265074000174300ustar00rootroot00000000000000-- rumur_exit_code: 1 -- A test that we can correctly escape characters in strings. var x: boolean; startstate begin x := true; end; rule "hello\" begin x := !x; end; rumur-2020.02.17/tests/string-escape2.m000066400000000000000000000002331362265074000174300ustar00rootroot00000000000000-- A test that we can correctly escape characters in strings. var x: boolean; startstate begin x := true; end; rule "hello\\" begin x := !x; end; rumur-2020.02.17/tests/string-escape3.m000066400000000000000000000002351362265074000174330ustar00rootroot00000000000000-- A test that we can correctly escape characters in strings. var x: boolean; startstate begin x := true; end; rule "\"hello\"" begin x := !x; end; rumur-2020.02.17/tests/string-injection.m000066400000000000000000000006421362265074000200740ustar00rootroot00000000000000/* A more generalised version of error-string-injection.m, where we try printf * format codes in several potentially vulnerable locations. */ var x: boolean startstate "hello %s world 1" begin x := true; end rule "hello %s world 2" begin if false then error "hello %s world 3"; end; assert "hello %s world 4" x | !x; x := !x; end assume "hello %s world 5" x | x; cover "hello %s world 6" x | x; rumur-2020.02.17/tests/switch-nested.m000066400000000000000000000004761362265074000173740ustar00rootroot00000000000000-- Test of a nested switch statement var x: 0 .. 10; startstate begin x := 5; end; rule begin switch x case 0, 1, 2, 3, 4, 5: switch x case 0: x := x + 1; else x := x - 1; end; else x := 10 - x; end; end; rule begin x := 10 - x; end; rumur-2020.02.17/tests/switch-stmt1.m000066400000000000000000000023651362265074000171610ustar00rootroot00000000000000-- Basic test of a switch statement var x: 0 .. 10; y: boolean; z: enum { A, B, C }; startstate begin x := 1; y := true; z := A; end; rule begin -- Switch on a state variable switch x case 1: x := x + 1; -- Empty case case 2: -- Multiple match case 3, 4: x := x + 1; else x := 10 - x; end; -- Switch on a non-lvalue switch 10 - x case 1: x := x - 1; else x := 10 - x; end; -- Switch with no else switch x case 1: x := x + 1; end; -- Switch with no cases switch x end; -- Switch on literal switch 10 case 3: x := 10 - x; case 10: x := 10 - x; end; -- Switch on boolean switch y case false: y := !y; case true: y := !y; end; -- Switch on boolean literal switch true case false: y := !y; case true: y := !y; end; -- Switch on enum switch z case A: z := A; case B: z := B; case C: z := C; -- Note, this is unreachable but should still be allowed else z := A; end; -- Switch on enum literal switch A case A: z := A; case B: z := B; end; end; rule begin x := 10 - x; end; rumur-2020.02.17/tests/switch-stmt2.m000066400000000000000000000004611362265074000171550ustar00rootroot00000000000000-- Test of switch statement with non-constant cases. This is a Rumur extension. var x: 0 .. 10; y: 0 .. 10; startstate begin x := 0; y := 1; end; rule begin switch x case y: x := 10 - x; case 5: x := 10 - x; end; end; rule begin x := 10 - x; y := 10 - y; end; rumur-2020.02.17/tests/switch-stmt3.m000066400000000000000000000005721362265074000171610ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test that switching on complex type is rejected var x: record y: boolean; end; startstate begin x.y := true; end; rule begin switch x -- There's nothing reasonable we can write as the cases here, but stress the -- validation logic even more by giving it no cases that it could detect as -- something to reject. end; end; rumur-2020.02.17/tests/ternary-operator.m000066400000000000000000000003441362265074000201220ustar00rootroot00000000000000/* Initially due to an oversight, Rumur did not support the ternary operator. * This tests that we have not reintroduced that bug. */ var x: boolean; startstate begin x := true; end; rule begin x := x ? !x : !x; end; rumur-2020.02.17/tests/trivial-function.m000066400000000000000000000003321362265074000200770ustar00rootroot00000000000000var x: boolean; procedure foo(); begin end; function bar(): boolean; begin return true; end; startstate begin x := true; end; rule begin x := bar(); end; rule begin foo(); end; rule begin x := !x; end; rumur-2020.02.17/tests/type-shadowing.m000066400000000000000000000003601362265074000175450ustar00rootroot00000000000000-- test that shadowing a type with a function parameter works as expected type t: 0 .. 1; var x: boolean; function foo(t: boolean): boolean; begin return !t; end; startstate begin x := true; end; rule begin x := foo(x); end; rumur-2020.02.17/tests/type-shadowing2.m000066400000000000000000000007631362265074000176360ustar00rootroot00000000000000-- Test that a function parameter does not prevent referencing a type of the -- same name in the return type. This is pretty strange code to write, but it -- falls into that "you know what I meant" category. Also, it is possible for a -- code generator to inadvertently produce code like this. type t: 0 .. 1; var x: boolean; function foo(t: boolean): t; begin return 0; end; startstate begin x := true; end; rule begin if foo(x) = 0 then x := !x; else x := !x; end; end; rumur-2020.02.17/tests/uint64-model.m000066400000000000000000000003361362265074000170350ustar00rootroot00000000000000/* this model is designed to require values out of the range of int64_t to test * uint64_t is usable */ var x: 0 .. 0xfffffffffffffffe; startstate begin x := 0; end; rule begin x := 0xfffffffffffffffe - x; end; rumur-2020.02.17/tests/uint64-model2.m000066400000000000000000000006461362265074000171230ustar00rootroot00000000000000-- rumur_exit_code: 1 /* A variant of uint64-model.m that tests for a mistake I first made wherein the * `--value-type auto` logic did not account for the fact that we need an extra * value to store `undefined`. I.e. If we only have types up to uint64_t, we * cannot represent a full 64-bit range. */ var x: 0 .. 0xffffffffffffffff; startstate begin x := 0; end; rule begin x := 0xffffffffffffffff - x; end; rumur-2020.02.17/tests/undefine-aggregate.m000066400000000000000000000005111362265074000203220ustar00rootroot00000000000000-- rumur_flags: ['--deadlock-detection', 'stuck'] -- test that we can 'undefine' an aggregate var x: record a: boolean; b: boolean; end; y: boolean; startstate begin x.a := true; x.b := false; y := true; end; rule y ==> begin x.a := !x.a; x.b := !x.b; end; rule begin undefine x; y := false; end; rumur-2020.02.17/tests/unicode-assignment.m000066400000000000000000000002151362265074000203760ustar00rootroot00000000000000-- Test the alternative form of the assignment operator var x: boolean; startstate begin x ≔ true; end; rule begin x ≔ !x; end; rumur-2020.02.17/tests/unused-record.m000066400000000000000000000002001362265074000173530ustar00rootroot00000000000000type foo_t: record x: boolean; end; var y: boolean; startstate begin y := false; end; rule begin y := !y; end; rumur-2020.02.17/tests/var-case.m000066400000000000000000000005641362265074000163120ustar00rootroot00000000000000-- This test is designed to provoke any issues with handling the -- case-sensitivity of variables. var state: boolean; State: boolean; sTate: boolean; stAte: boolean; staTe: boolean; statE: boolean; STATE: boolean; startstate begin state := true; State := false; end; rule begin state := !state; State := !State; end; invariant state != State; rumur-2020.02.17/tests/while-stmt1.m000066400000000000000000000006201362265074000167600ustar00rootroot00000000000000-- Basic test that while statements work var x: boolean; startstate begin x := true; end; rule begin -- While statement using a state variable while x do x := !x; end; -- While statement that uses an expression (non-lvalue) while (x | !x) & !x do x := !x; end; -- While statement using a constant while false do x := !x; end; end; rule begin x := !x; end; rumur-2020.02.17/tests/while-stmt2.m000066400000000000000000000005031362265074000167610ustar00rootroot00000000000000-- Test of a while statement using ranges var x: 0 .. 10; startstate begin x := 1; end; rule begin -- While statement using a state variable while x + 1 < 10 do x := x + 1; end; -- While statement using a constant expression while 1 + 1 > 10 do end; x := 5; end; rule begin x := 6 - x; end; rumur-2020.02.17/tests/while-stmt3.m000066400000000000000000000005261362265074000167670ustar00rootroot00000000000000-- Test that while statements work with record-nested fields var x: record y: boolean; end; z: record w: 0 .. 10; end; startstate begin x.y := true; z.w := 1; end; rule begin while x.y do x.y := !x.y; end; while z.w < 2 do z.w := 6 - z.w; end; end; rule begin x.y := !x.y; z.w := 6 - z.w; end; rumur-2020.02.17/tests/while-stmt4.m000066400000000000000000000002621362265074000167650ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test that ranges in while loops are rejected var x: 0 .. 10; startstate begin x := 5; end; rule begin while x do x := 6 - x; end; end; rumur-2020.02.17/tests/while-stmt5.m000066400000000000000000000003321362265074000167640ustar00rootroot00000000000000-- rumur_exit_code: 1 -- Test that complex types in while expressions are rejected var x: record y: boolean; end; startstate begin x.y := true; end; rule begin while x do x.y := !x.y; end; end; rumur-2020.02.17/tests/write-out-of-range.m000066400000000000000000000002171362265074000202370ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test writing a value out of range. var x: 0..1; startstate begin x := 0; end; rule begin x := x + 1; end; rumur-2020.02.17/tests/write-out-of-range2.m000066400000000000000000000002701362265074000203200ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test writing a value out of range into a record. var x: record a: 0..1; end; startstate begin x.a := 0; end; rule begin x.a := x.a + 1; end; rumur-2020.02.17/tests/write-out-of-range3.m000066400000000000000000000002651362265074000203250ustar00rootroot00000000000000-- checker_exit_code: 1 -- Test writing a value out of range into an array. var x: array[0..1] of 0..1; startstate begin x[0] := 0; end; rule begin x[0] := x[0] + 1; end; rumur-2020.02.17/tests/xml-escape-increment.m000066400000000000000000000012041362265074000206210ustar00rootroot00000000000000-- checker_output: re.compile(r'Rule "foo"') if self.xml else None -- checker_exit_code: 1 /* This tests for the presence of a bug first observed on commit * d3517bbf0c443242704979a4684777a9f1aeddfa. Rule names and other strings dealt * with by xml_escape() in the verifier were treated incorrectly. For characters * that do not need escaping, the output pointer is not incremented, resulting * in them being omitted. If the bug has been re-introduced, you will not see * Rule foo indicated in the counter-example trace. */ var x: boolean; startstate begin x := true; end; rule "foo" begin x := !x; end; invariant x;