├── .circleci └── config.yml ├── .clang-files ├── .clang-format ├── .github └── workflows │ └── release.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── ci ├── build_maximally_static.sh └── run_commands.sh ├── scripts ├── check_clang-format.sh └── do_clang-format.sh ├── src ├── CMakeLists.txt ├── ChcInterpreter.cc ├── ChcInterpreter.h ├── ChcSystem.cc ├── ChcSystem.h ├── ModelBasedProjection.cc ├── ModelBasedProjection.h ├── Normalizer.cc ├── Normalizer.h ├── Options.cc ├── Options.h ├── QuantifierElimination.cc ├── QuantifierElimination.h ├── TermUtils.cc ├── TermUtils.h ├── TransformationUtils.cc ├── TransformationUtils.h ├── TransitionSystem.cc ├── TransitionSystem.h ├── Validator.cc ├── Validator.h ├── Witnesses.cc ├── Witnesses.h ├── bin │ ├── CMakeLists.txt │ └── golem.cpp ├── engine │ ├── ArgBasedEngine.cc │ ├── ArgBasedEngine.h │ ├── Bmc.cc │ ├── Bmc.h │ ├── Common.cc │ ├── Common.h │ ├── DAR.cc │ ├── DAR.h │ ├── Engine.h │ ├── EngineFactory.cc │ ├── EngineFactory.h │ ├── IMC.cc │ ├── IMC.h │ ├── Kind.cc │ ├── Kind.h │ ├── Lawi.cc │ ├── Lawi.h │ ├── PDKind.cc │ ├── PDKind.h │ ├── Spacer.cc │ ├── Spacer.h │ ├── SymbolicExecution.cc │ ├── SymbolicExecution.h │ ├── TPA.cc │ ├── TPA.h │ ├── TransitionSystemEngine.cc │ └── TransitionSystemEngine.h ├── graph │ ├── ChcGraph.cc │ ├── ChcGraph.h │ ├── ChcGraphBuilder.cc │ └── ChcGraphBuilder.h ├── include │ ├── osmt_parser.h │ ├── osmt_solver.h │ └── osmt_terms.h ├── proofs │ ├── ProofSteps.cc │ ├── ProofSteps.h │ ├── Term.cc │ └── Term.h ├── transformers │ ├── BasicTransformationPipelines.cc │ ├── BasicTransformationPipelines.h │ ├── CommonUtils.cc │ ├── CommonUtils.h │ ├── ConstraintSimplifier.cc │ ├── ConstraintSimplifier.h │ ├── EdgeInliner.cc │ ├── EdgeInliner.h │ ├── FalseClauseRemoval.cc │ ├── FalseClauseRemoval.h │ ├── MultiEdgeMerger.cc │ ├── MultiEdgeMerger.h │ ├── NestedLoopTransformation.cc │ ├── NestedLoopTransformation.h │ ├── NodeEliminator.cc │ ├── NodeEliminator.h │ ├── RemoveUnreachableNodes.cc │ ├── RemoveUnreachableNodes.h │ ├── SimpleChainSummarizer.cc │ ├── SimpleChainSummarizer.h │ ├── SingleLoopTransformation.cc │ ├── SingleLoopTransformation.h │ ├── TransformationPipeline.cc │ ├── TransformationPipeline.h │ ├── Transformer.h │ ├── TrivialEdgePruner.cc │ └── TrivialEdgePruner.h └── utils │ ├── ScopeGuard.h │ ├── SmtSolver.cc │ ├── SmtSolver.h │ ├── StdUtils.h │ └── Timer.h └── test ├── CMakeLists.txt ├── TestTemplate.h ├── prooftesting ├── auxiliary-variables.smt2 ├── greater-than-simp.smt2 ├── ite.smt2 ├── let-terms.smt2 ├── negative_mod.smt2 ├── non-linearity-three-same-predicates-v2.smt2 ├── non-linearity-three-same-predicates.smt2 ├── non-linearity.smt2 ├── non-normalized_1.smt2 ├── non-normalized_2.smt2 ├── predicate_without_args.smt2 ├── proofcheck.py ├── real_constants.smt2 ├── real_negative_constants.smt2 ├── two-unused-vars.smt2 └── unused-qv.smt2 ├── test_BMC.cc ├── test_DAR.cc ├── test_IMC.cc ├── test_KIND.cc ├── test_LAWI.cc ├── test_MBP.cc ├── test_NNF.cc ├── test_Normalizer.cc ├── test_PDKIND.cc ├── test_PredicateAbstraction.cc ├── test_QE.cc ├── test_Spacer.cc ├── test_SymbolicExecution.cc ├── test_TPA.cc ├── test_TermUtils.cc ├── test_TransformationUtils.cc └── test_Transformers.cc /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | formatter: 4 | docker: 5 | - image: usiverify/verify-env:archlinux 6 | auth: 7 | username: mydockerhub-user 8 | password: $DOCKERHUB_PASSWORD 9 | 10 | steps: 11 | - checkout 12 | - run: 13 | name: run clang formatter 14 | command: | 15 | clang-format --version 16 | cat .clang-files | xargs clang-format --Werror --dry-run 17 | 18 | build-recent-gcc-debug: 19 | docker: 20 | - image: usiverify/verify-env:current 21 | auth: 22 | username: mydockerhub-user 23 | password: $DOCKERHUB_PASSWORD 24 | environment: 25 | CMAKE_BUILD_TYPE: Debug 26 | FLAGS: -Wall -Wextra -Werror 27 | 28 | steps: 29 | - checkout 30 | - run: 31 | name: Debug build gcc 32 | command: ./ci/run_commands.sh 33 | 34 | 35 | build-recent-gcc-release: 36 | docker: 37 | - image: usiverify/verify-env:current 38 | auth: 39 | username: mydockerhub-user 40 | password: $DOCKERHUB_PASSWORD 41 | environment: 42 | CMAKE_BUILD_TYPE: Release 43 | FLAGS: -Wall -Wextra -Werror 44 | 45 | steps: 46 | - checkout 47 | - run: 48 | name: Release build gcc 49 | command: ./ci/run_commands.sh 50 | 51 | 52 | build-recent-clang-debug: 53 | docker: 54 | - image: usiverify/verify-env:current 55 | auth: 56 | username: mydockerhub-user 57 | password: $DOCKERHUB_PASSWORD 58 | environment: 59 | CMAKE_BUILD_TYPE: Debug 60 | FLAGS: -Wall -Wextra -Werror -fsanitize=signed-integer-overflow,address,undefined 61 | 62 | steps: 63 | - checkout 64 | - run: 65 | name: Setup clang 66 | command: sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 60 67 | - run: 68 | name: Debug build clang 69 | command: ./ci/run_commands.sh 70 | 71 | 72 | build-recent-clang-release: 73 | docker: 74 | - image: usiverify/verify-env:current 75 | auth: 76 | username: mydockerhub-user 77 | password: $DOCKERHUB_PASSWORD 78 | environment: 79 | CMAKE_BUILD_TYPE: Release 80 | FLAGS: -Wall -Wextra -Werror 81 | 82 | steps: 83 | - checkout 84 | - run: 85 | name: Setup clang 86 | command: sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 60 87 | - run: 88 | name: Release build clang 89 | command: ./ci/run_commands.sh 90 | 91 | 92 | build-fedora-debug: 93 | docker: 94 | - image: usiverify/verify-env:fedora 95 | auth: 96 | username: mydockerhub-user 97 | password: $DOCKERHUB_PASSWORD 98 | environment: 99 | CMAKE_BUILD_TYPE: Debug 100 | FLAGS: -Wall -Wextra -Werror 101 | 102 | steps: 103 | - checkout 104 | - run: 105 | name: Debug build gcc 106 | command: | 107 | bash ./ci/run_commands.sh 108 | 109 | 110 | build-fedora-release: 111 | docker: 112 | - image: usiverify/verify-env:fedora 113 | auth: 114 | username: mydockerhub-user 115 | password: $DOCKERHUB_PASSWORD 116 | environment: 117 | CMAKE_BUILD_TYPE: Release 118 | FLAGS: -Wall -Wextra -Werror -Wno-free-nonheap-object # -Wno-free-nonheap-object added to work around false positive of GCC 119 | 120 | steps: 121 | - checkout 122 | - run: 123 | name: Release build gcc 124 | command: | 125 | bash ./ci/build_maximally_static.sh 126 | 127 | 128 | build-macos: 129 | macos: 130 | xcode: 15.4.0 131 | resource_class: macos.m1.medium.gen1 132 | steps: 133 | - run: echo "HOMEBREW_NO_AUTO_UPDATE=1" >> $BASH_ENV 134 | - run: 135 | name: Packages 136 | command: | 137 | brew install cmake 138 | brew install gmp 139 | brew install bison 140 | brew install flex 141 | - checkout 142 | - run: 143 | name: Store path 144 | command: echo 'export PATH=/opt/homebrew/opt/flex/bin:/opt/homebrew/opt/bison/bin:$PATH' >> $BASH_ENV 145 | - run: 146 | name: Release build OS X 147 | command: ./ci/run_commands.sh 148 | environment: 149 | CMAKE_BUILD_TYPE: Release 150 | FLAGS: -Wall -Wextra -Werror 151 | 152 | workflows: 153 | build-test: 154 | jobs: 155 | - formatter 156 | - build-fedora-debug: 157 | filters: &filters-build-test 158 | tags: 159 | only: /^v.*/ 160 | - build-fedora-release: 161 | filters: 162 | <<: *filters-build-test 163 | - build-recent-clang-debug: 164 | filters: 165 | <<: *filters-build-test 166 | - build-recent-clang-release: 167 | filters: 168 | <<: *filters-build-test 169 | - build-recent-gcc-debug: 170 | filters: 171 | <<: *filters-build-test 172 | - build-recent-gcc-release: 173 | filters: 174 | <<: *filters-build-test 175 | - build-macos: 176 | filters: 177 | <<: *filters-build-test 178 | -------------------------------------------------------------------------------- /.clang-files: -------------------------------------------------------------------------------- 1 | src/ModelBasedProjection.cc 2 | src/ModelBasedProjection.h 3 | src/Options.h 4 | src/TermUtils.cc 5 | src/TermUtils.h 6 | src/TransformationUtils.cc 7 | src/TransformationUtils.h 8 | src/TransitionSystem.cc 9 | src/TransitionSystem.h 10 | src/ChcInterpreter.cc 11 | src/ChcInterpreter.h 12 | src/QuantifierElimination.cc 13 | src/QuantifierElimination.h 14 | 15 | src/engine/Bmc.cc 16 | src/engine/Bmc.h 17 | src/engine/DAR.cc 18 | src/engine/DAR.h 19 | src/engine/IMC.cc 20 | src/engine/IMC.h 21 | src/engine/Lawi.cc 22 | src/engine/Lawi.h 23 | src/engine/PDKind.cc 24 | src/engine/PDKind.h 25 | src/engine/SymbolicExecution.cc 26 | src/engine/SymbolicExecution.h 27 | src/engine/Spacer.cc 28 | src/engine/Spacer.h 29 | src/engine/TPA.cc 30 | src/engine/TPA.h 31 | src/engine/Common.cc 32 | src/engine/Common.h 33 | src/engine/EngineFactory.cc 34 | src/engine/EngineFactory.h 35 | src/engine/ArgBasedEngine.cc 36 | src/engine/ArgBasedEngine.h 37 | 38 | src/graph/ChcGraph.cc 39 | src/graph/ChcGraph.h 40 | 41 | src/proofs/ProofSteps.cc 42 | src/proofs/ProofSteps.h 43 | src/proofs/Term.cc 44 | src/proofs/Term.h 45 | 46 | src/transformers/EdgeInliner.cc 47 | src/transformers/EdgeInliner.h 48 | src/transformers/MultiEdgeMerger.cc 49 | src/transformers/MultiEdgeMerger.h 50 | src/transformers/SingleLoopTransformation.cc 51 | src/transformers/SingleLoopTransformation.h 52 | src/transformers/NestedLoopTransformation.cc 53 | src/transformers/NestedLoopTransformation.h 54 | src/transformers/RemoveUnreachableNodes.cc 55 | src/transformers/RemoveUnreachableNodes.h 56 | src/transformers/TrivialEdgePruner.cc 57 | src/transformers/TrivialEdgePruner.h 58 | 59 | src/utils/SmtSolver.h 60 | src/utils/SmtSolver.cc 61 | src/utils/StdUtils.h 62 | src/utils/Timer.h 63 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | ColumnLimit: 120 5 | IndentWidth: 4 6 | AccessModifierOffset: -4 7 | PointerAlignment: Middle 8 | AllowShortFunctionsOnASingleLine: Inline 9 | AllowShortBlocksOnASingleLine: Always 10 | AllowShortIfStatementsOnASingleLine: WithoutElse 11 | IndentCaseLabels: True 12 | NamespaceIndentation: None 13 | PackConstructorInitializers: NextLine 14 | ReflowComments: False 15 | SpaceAfterTemplateKeyword: False 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Workflow 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | release-builds-osx: 9 | strategy: 10 | matrix: 11 | include: 12 | - os: macos-14 13 | suffix: arm-osx 14 | - os: macos-13 15 | suffix: x64-osx 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v3 20 | 21 | - name: Set up build environment 22 | run: | 23 | brew install bison 24 | brew install flex 25 | echo "/opt/homebrew/opt/flex/bin:/opt/homebrew/opt/bison/bin" >> "$GITHUB_PATH" 26 | echo "/usr/local/opt/flex/bin:/usr/local/opt/bison/bin" >> "$GITHUB_PATH" 27 | 28 | - name: Build project 29 | run: | 30 | ./ci/build_maximally_static.sh 31 | mv ./build-static/golem.tar.bz2 ./golem-${{ matrix.suffix }}.tar.bz2 32 | 33 | - name: Upload Release Asset 34 | uses: softprops/action-gh-release@v2 35 | with: 36 | files: ./golem-${{ matrix.suffix }}.tar.bz2 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | 40 | release-build-linux: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout code 44 | uses: actions/checkout@v3 45 | 46 | - name: Set up build environment 47 | run: | 48 | sudo apt-get update 49 | sudo apt-get install -y bison cmake flex libgmp-dev 50 | 51 | - name: Build project 52 | run: | 53 | ./ci/build_maximally_static.sh 54 | mv ./build-static/golem.tar.bz2 ./golem-x64-linux.tar.bz2 55 | 56 | - name: Upload Release Asset 57 | uses: softprops/action-gh-release@v2 58 | with: 59 | files: ./golem-x64-linux.tar.bz2 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | 3 | ## idea 4 | cmake-build-* 5 | .idea 6 | *.log 7 | run_benchmark.sh 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | set(GOLEM_VERSION_MAJOR 0) 4 | set(GOLEM_VERSION_MINOR 8) 5 | set(GOLEM_VERSION_PATCH 1) 6 | set(GOLEM_VERSION ${GOLEM_VERSION_MAJOR}.${GOLEM_VERSION_MINOR}.${GOLEM_VERSION_PATCH}) 7 | project(Golem VERSION ${GOLEM_VERSION} LANGUAGES CXX) 8 | 9 | add_definitions(-DGOLEM_VERSION="${GOLEM_VERSION}") 10 | 11 | set(CMAKE_CXX_STANDARD 20) 12 | 13 | set(CMAKE_SOURCE_DIR "src") 14 | 15 | set(VALID_BUILD_TYPES "Debug" "Release" "RelWithDebInfo" "MinSizeRel") 16 | 17 | #Set the default build type if this is the first time cmake is run and nothing has been set 18 | if (NOT EXISTS ${CMAKE_BINARY_DIR}/CMakeCache.txt) 19 | if (NOT CMAKE_BUILD_TYPE) 20 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) 21 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${VALID_BUILD_TYPES}) 22 | endif() 23 | endif() 24 | 25 | list(FIND VALID_BUILD_TYPES "${CMAKE_BUILD_TYPE}" _build_type_index) 26 | if(_build_type_index EQUAL -1) 27 | message(FATAL_ERROR "Invalid build type: ${CMAKE_BUILD_TYPE}. Valid options are: ${VALID_BUILD_TYPES}") 28 | endif() 29 | 30 | option(GOLEM_BUILD_TEST "Build the tests" ON) 31 | 32 | add_subdirectory(${CMAKE_SOURCE_DIR}) 33 | 34 | ################# TESTING ############################################# 35 | if(GOLEM_BUILD_TEST) 36 | enable_testing() 37 | add_subdirectory(${PROJECT_SOURCE_DIR}/test) 38 | endif() 39 | ######################################################################### 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 usi-verification-and-security 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golem 2 | 3 | [![CircleCI](https://dl.circleci.com/status-badge/img/gh/usi-verification-and-security/golem/tree/master.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/usi-verification-and-security/golem/tree/master) 4 | 5 | 6 | Golem is a solver for Constrained Horn Clauses (CHCs). 7 | It accepts the input in the format of (extended) SMT-LIB 2.6, as defined by [CHC-COMP](https://chc-comp.github.io/format.html). 8 | 9 | ## Installation 10 | The easiest way is to download the executables from our release page. This way, all dependencies are already bundled in the executable. 11 | 12 | ### Building from source 13 | Golem can be compiled on Linux and MacOS. 14 | It uses `CMake` for build configuration. 15 | Golem depends on [OpenSMT](https://github.com/usi-verification-and-security/opensmt/) for SMT solving and interpolation. 16 | If you already have OpenSMT installed, you can pass the path using `-DOPENSMT_HOME` option to `cmake` command. 17 | Note that Golem requires a specific version of OpenSMT, currently v2.9.1. 18 | Otherwise, `cmake` will download the latest compatible version of OpenSMT and build it as a subproject. 19 | 20 | ## Usage 21 | You can view the usage in the help message after running 22 | ``` 23 | $ golem -h 24 | ``` 25 | 26 | At the moment, you should specified the SMT theory used in the CHC encoding with `-l` option. The supported theories are `QF_LRA` and `QF_LIA`, i.e., the linear arithmetic over reals or integers. 27 | Golem now has limited support to automatically detect the theory from the script, so the option is no longer mandatory, but still recommended. 28 | 29 | ## Backend engines 30 | Golem supports several different backend algorithms for solving CHCs. 31 | 32 | ### Spacer (default) 33 | Spacer engine represents our own implementation of the Spacer algorithm from [this paper](https://link.springer.com/article/10.1007/s10703-016-0249-4). You might be familiar with the original implementation of Spacer inside [Z3](https://github.com/z3Prover/z3/). 34 | 35 | 36 | ### Bounded model checking 37 | 38 | BMC engine implements the simple bounded model checking algorithm which checks for existence of increasingly longer counterexample paths. 39 | It uses incremental capibilities of the underlying SMT solver to speed up the process. 40 | Works only for linear systems of Horn clauses. 41 | 42 | ### Dual Approximated Reachability 43 | 44 | DAR engine implements the algorithm Dual Approximated Reachability described in [this paper](https://link.springer.com/chapter/10.1007/978-3-642-36742-7_22). 45 | It works only for linear systems of Horn clauses. 46 | 47 | ### k-induction 48 | 49 | KIND engine implements very basic k-induction algorithm from [this paper](https://link.springer.com/chapter/10.1007/3-540-40922-X_8). 50 | It only supports transition systems. 51 | 52 | ### Lazy Abstraction With Interpolants (Impact) 53 | 54 | The implementation of LAWI follows the description of the algorithm in [this paper](https://link.springer.com/chapter/10.1007/11817963_14). 55 | The algorithm is also known as `Impact`, which was the first tool where the algorithm was implemented. 56 | Works only for linear systems of Horn clauses. 57 | 58 | ### McMillan's Interpolation-based model checking 59 | 60 | IMC engine implements the original McMillan's interpolation-based model-checking algorithm from [this paper](https://link.springer.com/chapter/10.1007/978-3-540-45069-6_1). 61 | It works on transition system, but it can handle linear systems of Horn clauses by first transforming them into a simple transition system. 62 | 63 | ### Predicate Abstraction and CEGAR 64 | 65 | The PA engine is a simple prototype of a [predicate abstraction](https://link.springer.com/chapter/10.1007/3-540-63166-6_10) with [CEGAR](https://link.springer.com/chapter/10.1007/10722167_15). 66 | The implementation is still rather naive, but the algorithm can handle all (even nonlinear) CHC systems. 67 | 68 | ### Property-directed k-induction 69 | 70 | The implementation of PDKIND follows the description of the algorithm in [this paper](https://ieeexplore.ieee.org/document/7886665). 71 | It works on transition system, but it can handle linear systems of Horn clauses by first transforming them into a simple transition system. 72 | 73 | ### Symbolic Execution 74 | Following the description from [this paper](https://link.springer.com/chapter/10.1007/978-3-031-50524-9_13), symbolic execution engine implements a *forward* exploration of the reachability graph where reachable nodes are characterized precisely by their path formula (non-current-state variables are implicitly existentially quantified). 75 | Current implementation supports only linear CHC systems. 76 | 77 | ### Transition Power Abstraction 78 | 79 | TPA is an algorithm we have developed recently with the goal to detect long counterexample quickly. The description of the algorithm can be found in [this paper](https://link.springer.com/chapter/10.1007/978-3-030-99524-9_29). 80 | TPA directly supports a subset of linear CHC systems which can be mapped to DAGs of transition systems. 81 | Transitions that do not fall into this category are handled by transformation into a simple transition system. 82 | 83 | [split-TPA](https://ieeexplore.ieee.org/document/10026590) is a different instantiation of the TPA paradigm and is typically more powerful than basic TPA on satisfiable (safe) CHC systems. 84 | 85 | 86 | #### Running multiple engines in parallel 87 | 88 | Golem also supports multiprocess run of several engines simultaneously. 89 | You can pass comma-separated list of engines to `--engine` options. 90 | For example, the following invocation will run split-TPA, Spacer and LAWI in parallel 91 | 92 | ```sh 93 | golem --engine split-tpa,spacer,lawi --input 94 | ``` 95 | 96 | ### Witness validation and printing 97 | Golem supports internal validation of witnesses for its answer using `--validate` option. 98 | Witness for `sat` is a model, an interpretation of the predicates. 99 | Witness for `unsat` is a proof. 100 | 101 | To obtain the produced model or proof of unsatisfiability, use `--print-witness`. 102 | -------------------------------------------------------------------------------- /ci/build_maximally_static.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | build_folder="build-static" 5 | if [ -d ${build_folder} ]; then rm -rf ${build_folder}; fi 6 | mkdir -p ${build_folder} 7 | cd ${build_folder} 8 | 9 | if [ ! -z ${CMAKE_CXX_COMPILER} ]; then 10 | COMPILER_OPTION="-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" 11 | fi 12 | 13 | if [[ $OSTYPE != "darwin"* ]]; then 14 | LINKER_OPTIONS="-DCMAKE_EXE_LINKER_FLAGS=-static" 15 | fi 16 | 17 | cmake -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ 18 | -DCMAKE_CXX_FLAGS="${FLAGS}" \ 19 | -DCMAKE_INSTALL_PREFIX=${OSMT_INSTALL} \ 20 | -DMAXIMALLY_STATIC_BINARY=YES\ 21 | ${COMPILER_OPTION} \ 22 | ${LINKER_OPTIONS} \ 23 | .. 24 | 25 | cmake --build . -j 4 26 | 27 | strip golem 28 | 29 | tar jcf golem.tar.bz2 golem 30 | 31 | echo "Placed stripped, maximally static binary in $(pwd)/golem.tar.bz2" 32 | -------------------------------------------------------------------------------- /ci/run_commands.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ev 4 | if [ -d build ]; then rm -rf build; fi 5 | mkdir -p build 6 | cd build 7 | 8 | if [ ! -z ${CMAKE_CXX_COMPILER} ]; then 9 | COMPILER_OPTION="-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" 10 | fi 11 | 12 | cmake -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \ 13 | -DCMAKE_CXX_FLAGS="${FLAGS}" \ 14 | ${COMPILER_OPTION} \ 15 | .. 16 | 17 | cmake --build . -j 4 18 | ctest 19 | 20 | -------------------------------------------------------------------------------- /scripts/check_clang-format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | xargs clang-format --Werror --dry-run <.clang-files 4 | -------------------------------------------------------------------------------- /scripts/do_clang-format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | xargs clang-format -i <.clang-files 4 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | # Find OpenSMT2 4 | SET(OPENSMT_HOME CACHE STRING "OpenSMT installation directory") 5 | find_package(OpenSMT 2.9.1 EXACT CONFIG PATHS ${OPENSMT_HOME} NO_DEFAULT_PATH) 6 | 7 | if (OpenSMT_FOUND) 8 | else(OpenSMT_FOUND) 9 | FetchContent_Declare( 10 | OpenSMT 11 | GIT_REPOSITORY https://github.com/usi-verification-and-security/opensmt.git 12 | GIT_TAG a14fd65b7fe605ecccd7eb58e5e71218759bdba4 # v2.9.1 13 | GIT_SHALLOW true 14 | GIT_PROGRESS true 15 | ) 16 | #set(FETCHCONTENT_QUIET OFF) 17 | set(BUILD_EXECUTABLES OFF CACHE INTERNAL "") 18 | set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "") 19 | set(BUILD_STATIC_LIBS ON CACHE INTERNAL "") 20 | set(PACKAGE_TESTS OFF CACHE INTERNAL "") 21 | message("Getting OpenSMT...") 22 | FetchContent_MakeAvailable(OpenSMT) 23 | message("OpenSMT downloaded") 24 | add_library(OpenSMT::OpenSMT ALIAS OpenSMT-static) 25 | endif(OpenSMT_FOUND) 26 | 27 | add_library(golem_lib OBJECT "") 28 | 29 | target_link_libraries(golem_lib PUBLIC OpenSMT::OpenSMT) 30 | 31 | target_sources(golem_lib 32 | PRIVATE ChcSystem.cc 33 | PRIVATE ChcInterpreter.cc 34 | PRIVATE engine/ArgBasedEngine.cc 35 | PRIVATE engine/Bmc.cc 36 | PRIVATE engine/Common.cc 37 | PRIVATE engine/DAR.cc 38 | PRIVATE engine/EngineFactory.cc 39 | PRIVATE engine/IMC.cc 40 | PRIVATE engine/Kind.cc 41 | PRIVATE engine/Lawi.cc 42 | PRIVATE engine/PDKind.cc 43 | PRIVATE engine/Spacer.cc 44 | PRIVATE engine/SymbolicExecution.cc 45 | PRIVATE engine/TPA.cc 46 | PRIVATE engine/TransitionSystemEngine.cc 47 | PRIVATE TransitionSystem.cc 48 | PRIVATE Options.cc 49 | PRIVATE TermUtils.cc 50 | PRIVATE TransformationUtils.cc 51 | PRIVATE Validator.cc 52 | PRIVATE Normalizer.cc 53 | PRIVATE Witnesses.cc 54 | PRIVATE proofs/Term.h 55 | PRIVATE proofs/Term.cc 56 | PRIVATE proofs/ProofSteps.h 57 | PRIVATE proofs/ProofSteps.cc 58 | PRIVATE ModelBasedProjection.cc 59 | PRIVATE QuantifierElimination.cc 60 | PRIVATE graph/ChcGraph.cc 61 | PRIVATE graph/ChcGraphBuilder.cc 62 | PRIVATE transformers/BasicTransformationPipelines.cc 63 | PRIVATE transformers/CommonUtils.cc 64 | PRIVATE transformers/ConstraintSimplifier.cc 65 | PRIVATE transformers/EdgeInliner.cc 66 | PRIVATE transformers/SimpleChainSummarizer.cc 67 | PRIVATE transformers/NodeEliminator.cc 68 | PRIVATE transformers/MultiEdgeMerger.cc 69 | PRIVATE transformers/FalseClauseRemoval.cc 70 | PRIVATE transformers/RemoveUnreachableNodes.cc 71 | PRIVATE transformers/NestedLoopTransformation.cc 72 | PRIVATE transformers/SingleLoopTransformation.cc 73 | PRIVATE transformers/TransformationPipeline.cc 74 | PRIVATE transformers/TrivialEdgePruner.cc 75 | PRIVATE utils/SmtSolver.cc 76 | ) 77 | 78 | target_include_directories(golem_lib PUBLIC ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/include) 79 | 80 | 81 | 82 | if (NOT OpenSMT_FOUND) 83 | target_compile_definitions(golem_lib PUBLIC OPENSMT_LOCAL_BUILD) 84 | #message("Directories:${opensmt_SOURCE_DIR}; ${opensmt_BINARY_DIR}") 85 | target_include_directories(golem_lib PUBLIC "${opensmt_SOURCE_DIR}/src") 86 | #message("GMP dirs:${GMP_INCLUDE_DIR}") 87 | target_include_directories(golem_lib PUBLIC ${GMP_INCLUDE_DIR}) 88 | endif() 89 | 90 | 91 | add_subdirectory(bin) 92 | -------------------------------------------------------------------------------- /src/ChcInterpreter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_CHCINTERPRETER_H 8 | #define GOLEM_CHCINTERPRETER_H 9 | 10 | #include "ChcSystem.h" 11 | #include "Normalizer.h" 12 | #include "Options.h" 13 | 14 | #include "proofs/Term.h" 15 | #include "transformers/Transformer.h" 16 | 17 | #include "osmt_parser.h" 18 | 19 | #include 20 | #include 21 | 22 | namespace golem { 23 | class LetBinder { 24 | PTRef currentValue; 25 | std::vector * shadowedValues; 26 | 27 | public: 28 | explicit LetBinder(PTRef val) : currentValue(val), shadowedValues(nullptr) {} 29 | ~LetBinder() { delete shadowedValues; } 30 | LetBinder(const LetBinder &) = delete; 31 | LetBinder & operator=(const LetBinder &) = delete; 32 | LetBinder(LetBinder &&) = default; 33 | LetBinder & operator=(LetBinder &&) = default; 34 | PTRef getValue() const { return currentValue; } 35 | bool hasShadowValue() const { return shadowedValues && !shadowedValues->empty(); } 36 | void restoreShadowedValue() { 37 | assert(hasShadowValue()); 38 | currentValue = shadowedValues->back(); 39 | shadowedValues->pop_back(); 40 | } 41 | void addValue(PTRef val) { 42 | if (not shadowedValues) { shadowedValues = new std::vector(); } 43 | shadowedValues->push_back(currentValue); 44 | currentValue = val; 45 | } 46 | }; 47 | 48 | class LetRecords { 49 | std::unordered_map> letBinders; 50 | std::vector knownBinders; 51 | std::vector frameLimits; 52 | 53 | bool has(const char * name) const { return letBinders.count(name) != 0; } 54 | 55 | public: 56 | PTRef getOrUndef(const char * letSymbol) const { 57 | auto it = letBinders.find(letSymbol); 58 | if (it != letBinders.end()) { return it->second.getValue(); } 59 | return PTRef_Undef; 60 | } 61 | void pushFrame() { frameLimits.push_back(knownBinders.size()); } 62 | void popFrame() { 63 | auto limit = frameLimits.back(); 64 | frameLimits.pop_back(); 65 | while (knownBinders.size() > limit) { 66 | const char * binder = knownBinders.back(); 67 | knownBinders.pop_back(); 68 | assert(this->has(binder)); 69 | auto & values = letBinders.at(binder); 70 | if (values.hasShadowValue()) { 71 | values.restoreShadowedValue(); 72 | } else { 73 | letBinders.erase(binder); 74 | } 75 | } 76 | } 77 | 78 | void addBinding(const char * name, PTRef arg) { 79 | knownBinders.push_back(name); 80 | if (not this->has(name)) { 81 | letBinders.insert(std::make_pair(name, LetBinder(arg))); 82 | } else { 83 | letBinders.at(name).addValue(arg); 84 | } 85 | } 86 | }; 87 | 88 | class ChcInterpreterContext { 89 | public: 90 | std::unique_ptr interpretSystemAst(const ASTNode * root); 91 | ChcInterpreterContext(ArithLogic & logic, Options const & opts) : logic(logic), opts(opts) {} 92 | 93 | std::vector operators = {"+", "-", "/", "*", "and", "or", "=>", "not", 94 | "=", ">=", "<=", ">", "<", "ite", "mod", "div"}; 95 | 96 | bool isOperator(const std::string & val) { 97 | for (const std::string & op : operators) { 98 | if (op == val) { return true; } 99 | } 100 | return false; 101 | } 102 | 103 | private: 104 | ArithLogic & logic; 105 | Options const & opts; 106 | std::unique_ptr system; 107 | std::vector> originalAssertions; 108 | bool doExit = false; 109 | LetRecords letRecords; 110 | 111 | void interpretCommand(ASTNode & node); 112 | 113 | void interpretDeclareFun(ASTNode & node); 114 | 115 | void interpretAssert(ASTNode & node); 116 | 117 | void interpretCheckSat(); 118 | 119 | static void reportError(std::string const & msg); 120 | 121 | VerificationResult solve(std::string const & engine, ChcDirectedHyperGraph const & hyperGraph); 122 | 123 | [[nodiscard]] bool hasWorkAfterAnswer() const; 124 | 125 | void doWorkAfterAnswer(VerificationResult result, ChcDirectedHyperGraph const & originalGraph, 126 | WitnessBackTranslator & translator, 127 | Normalizer::Equalities const & normalizingEqualities) const; 128 | 129 | void solveWithMultipleEngines(std::string const & engines, std::unique_ptr hyperGraph, 130 | std::unique_ptr originalGraph, 131 | std::unique_ptr translator, 132 | Normalizer::Equalities const & normalizingEqualities); 133 | 134 | [[nodiscard]] SRef sortFromASTNode(ASTNode const & node) const; 135 | 136 | PTRef parseTopLevelAssertion(ASTNode const & node); 137 | PTRef parseTerm(ASTNode const & node); 138 | 139 | std::shared_ptr ASTtoTerm(ASTNode const & node); 140 | 141 | // Building CHCs and helper methods 142 | 143 | std::optional chclauseFromPTRef(PTRef ref); 144 | 145 | bool isUninterpretedPredicate(PTRef ref) const; 146 | }; 147 | 148 | class ChcInterpreter { 149 | public: 150 | std::unique_ptr interpretSystemAst(ArithLogic & logic, const ASTNode * root); 151 | 152 | explicit ChcInterpreter(Options const & opts) : opts(opts) {} 153 | 154 | private: 155 | Options const & opts; 156 | }; 157 | } // namespace golem 158 | 159 | #endif // GOLEM_CHCINTERPRETER_H 160 | -------------------------------------------------------------------------------- /src/ChcSystem.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "ChcSystem.h" 8 | 9 | #include 10 | 11 | namespace golem { 12 | void ChcPrinter::print(const ChcSystem & system, std::ostream & out) const { 13 | auto const & clauses = system.getClauses(); 14 | for (auto const& clause : clauses) { 15 | print(clause, out); 16 | } 17 | } 18 | 19 | void ChcPrinter::print(const ChClause & clause, std::ostream & out) const { 20 | auto const & head = clause.head; 21 | std::string headStr = logic.printTerm(head.predicate.predicate); 22 | out << headStr << " :- " << '\n'; 23 | auto const & body = clause.body; 24 | for (auto const& predicate : body.uninterpretedPart) { 25 | out << '\t' << logic.printTerm(predicate.predicate) << ",\n"; 26 | } 27 | out << '\t' << logic.printTerm(body.interpretedPart.fla) << std::endl; 28 | } 29 | } // namespace golem 30 | -------------------------------------------------------------------------------- /src/ChcSystem.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_CHCSYSTEM_H 8 | #define GOLEM_CHCSYSTEM_H 9 | 10 | #include "osmt_terms.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace golem { 17 | struct UninterpretedPredicate { 18 | PTRef predicate; 19 | }; 20 | 21 | inline bool operator==(UninterpretedPredicate const& p1, UninterpretedPredicate const& p2) { return p1.predicate == p2.predicate; } 22 | inline bool operator!=(UninterpretedPredicate const& p1, UninterpretedPredicate const& p2) { return !(p1 == p2); } 23 | 24 | struct InterpretedFla { 25 | PTRef fla; 26 | }; 27 | 28 | inline bool operator==(InterpretedFla const & f1, InterpretedFla const & f2) { return f1.fla == f2.fla; } 29 | inline bool operator!=(InterpretedFla const & f1, InterpretedFla const & f2) { return not(f1 == f2); } 30 | 31 | struct ChcHead { 32 | UninterpretedPredicate predicate; 33 | }; 34 | 35 | inline bool operator==(ChcHead const & h1, ChcHead const & h2) { 36 | return h1.predicate == h2.predicate; 37 | } 38 | inline bool operator!=(ChcHead const & h1, ChcHead const & h2) { return not(h1 == h2); } 39 | 40 | struct ChcBody { 41 | InterpretedFla interpretedPart; 42 | std::vector uninterpretedPart; 43 | }; 44 | 45 | inline bool operator==(ChcBody const & b1, ChcBody const & b2) { 46 | if (b1.interpretedPart != b2.interpretedPart) { return false; } 47 | if (b1.uninterpretedPart.size() != b2.uninterpretedPart.size()) { return false; } 48 | for (std::size_t i = 0; i < b1.uninterpretedPart.size(); ++i) { 49 | if (b1.uninterpretedPart[i] != b2.uninterpretedPart[i]) { return false; } 50 | } 51 | return true; 52 | } 53 | inline bool operator!=(ChcBody const & b1, ChcBody const & b2) { return not(b1 == b2); } 54 | 55 | struct ChClause { 56 | ChcHead head; 57 | ChcBody body; 58 | }; 59 | 60 | inline bool operator== (ChClause const & c1, ChClause const & c2) { 61 | return c1.head == c2.head && c1.body == c2.body; 62 | } 63 | 64 | class ChcSystem { 65 | std::vector clauses; 66 | std::unordered_set knownUninterpretedPredicates; 67 | 68 | public: 69 | void addUninterpretedPredicate(SymRef sym) { knownUninterpretedPredicates.insert(sym); } 70 | 71 | bool isUninterpretedPredicate(SymRef sym) { return knownUninterpretedPredicates.count(sym) != 0; } 72 | 73 | void addClause(ChClause clause) { clauses.push_back(std::move(clause)); } 74 | void addClause(ChcHead head, ChcBody body) { clauses.push_back(ChClause{.head = std::move(head), .body = std::move(body)}); } 75 | 76 | std::vector const & getClauses() const { return clauses; } 77 | 78 | 79 | }; 80 | 81 | class ChcPrinter { 82 | Logic& logic; 83 | std::ostream * defaultOut; 84 | public: 85 | ChcPrinter(Logic& logic, std::ostream & out) : logic(logic), defaultOut(&out) {} 86 | ChcPrinter(Logic& logic) : logic(logic), defaultOut(nullptr) {} 87 | 88 | void print(ChcSystem const & system, std::ostream & out) const; 89 | void print(ChClause const & clause, std::ostream & out) const; 90 | void print(ChcSystem const & system) const { if (defaultOut) { print(system, *defaultOut); } } 91 | void print(ChClause const & clause) const { if (defaultOut) { print(clause, *defaultOut); } } 92 | }; 93 | 94 | class PTRefToCHC { 95 | public: 96 | static ChcHead constructHead(PTRef head) { return ChcHead{head};} 97 | static ChcBody constructBody(PTRef interpreted, std::vector const & uninterpreted) { 98 | return constructBody(interpreted, uninterpreted.begin(), uninterpreted.end()); 99 | } 100 | template 101 | static ChcBody constructBody(PTRef interpreted, TIt uninterpretedBegin, TIt uninterpretedEnd) { 102 | std::vector uninterpretedPart; 103 | std::transform(uninterpretedBegin, uninterpretedEnd, std::back_inserter(uninterpretedPart), [](PTRef ref) { 104 | return UninterpretedPredicate{.predicate = ref}; 105 | }); 106 | return ChcBody{{interpreted}, std::move(uninterpretedPart)}; 107 | } 108 | }; 109 | } // namespace golem 110 | #endif // GOLEM_CHCSYSTEM_H 111 | -------------------------------------------------------------------------------- /src/ModelBasedProjection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_MODELBASEDPROJECTION_H 8 | #define GOLEM_MODELBASEDPROJECTION_H 9 | 10 | #include "osmt_solver.h" 11 | #include "osmt_terms.h" 12 | 13 | #include 14 | #include 15 | 16 | namespace golem { 17 | class ModelBasedProjection { 18 | private: 19 | Logic & logic; 20 | 21 | public: 22 | using VarsInfo = Map; 23 | 24 | explicit ModelBasedProjection(Logic & logic) : logic(logic) {} 25 | 26 | PTRef project(PTRef fla, vec const & varsToEliminate, Model & model); 27 | 28 | PTRef keepOnly(PTRef fla, vec const & varsToKeep, Model & model); 29 | 30 | using implicant_t = std::vector; 31 | 32 | private: 33 | implicant_t projectSingleVar(PTRef var, implicant_t implicant, Model & model); 34 | 35 | implicant_t getImplicant(PTRef var, Model & model, VarsInfo const &); 36 | 37 | void dumpImplicant(std::ostream & out, implicant_t const & implicant); 38 | 39 | void postprocess(implicant_t & literals, ArithLogic & logic); 40 | 41 | // LIA version 42 | 43 | struct DivisibilityConstraint { 44 | PTRef constant; 45 | PTRef term; 46 | }; 47 | 48 | using div_constraints_t = std::vector; 49 | 50 | implicant_t projectIntegerVars(PTRef * beg, PTRef * end, implicant_t implicant, Model & model); 51 | 52 | void processDivConstraints(PTRef var, div_constraints_t & divConstraints, implicant_t & implicant, Model & model); 53 | 54 | void processClassicLiterals(PTRef var, div_constraints_t & divConstraints, implicant_t & implicant, Model & model); 55 | 56 | struct LIABound { 57 | PTRef term; 58 | PTRef coeff; 59 | }; 60 | 61 | struct LIABoundLower { 62 | PTRef term; 63 | PTRef coeff; 64 | }; 65 | struct LIABoundUpper { 66 | PTRef term; 67 | PTRef coeff; 68 | }; 69 | 70 | struct ResolveResult { 71 | std::vector bounds; 72 | DivisibilityConstraint constraint; // TODO: optional 73 | bool hasDivConstraint; 74 | }; 75 | 76 | ResolveResult resolve(LIABoundLower const & lower, LIABoundUpper const & upper, Model & model, 77 | ArithLogic & lialogic); 78 | }; 79 | } // namespace golem 80 | #endif // GOLEM_MODELBASEDPROJECTION_H 81 | -------------------------------------------------------------------------------- /src/Normalizer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "Normalizer.h" 8 | 9 | namespace golem { 10 | NormalizedChcSystem Normalizer::normalize(const ChcSystem & system) { 11 | this->canonicalPredicateRepresentation.addRepresentation(logic.getSym_true(), {}); 12 | std::vector normalized; 13 | auto const& clauses = system.getClauses(); 14 | for (auto const & clause : clauses) { 15 | normalized.push_back(normalize(clause)); 16 | } 17 | NonlinearCanonicalPredicateRepresentation cpr = getCanonicalPredicateRepresentation(); 18 | // build graph out of normalized system 19 | auto newSystem = std::make_unique(); 20 | for (auto const & clause : normalized) { 21 | newSystem->addClause(clause); 22 | } 23 | return NormalizedChcSystem{.normalizedSystem = std::move(newSystem), .canonicalPredicateRepresentation = std::move(cpr)}; 24 | } 25 | 26 | ChClause Normalizer::normalize(ChClause const & clause) { 27 | auto const & head = clause.head; 28 | auto const & body = clause.body; 29 | topLevelEqualities.clear(); 30 | ChcHead newHead = normalize(head); 31 | ChcBody newBody = normalize(body); 32 | ChClause res = normalizeAuxiliaryVariables(ChClause{.head = std::move(newHead), .body = std::move(newBody)}); 33 | normalizingEqualities.push_back(std::move(topLevelEqualities)); 34 | topLevelEqualities.clear(); 35 | return res; 36 | } 37 | 38 | ChClause Normalizer::normalizeAuxiliaryVariables(ChClause && clause) { 39 | TermUtils utils(logic); 40 | std::unordered_set predicateVars; 41 | // vars from head 42 | { 43 | auto headVars = utils.predicateArgsInOrder(clause.head.predicate.predicate); 44 | predicateVars.insert(headVars.begin(), headVars.end()); 45 | } 46 | // vars from uninterpreted body 47 | for (auto const & pred : clause.body.uninterpretedPart) { 48 | auto vars = utils.predicateArgsInOrder(pred.predicate); 49 | predicateVars.insert(vars.begin(), vars.end()); 50 | } 51 | 52 | PTRef newInterpretedBody = clause.body.interpretedPart.fla; 53 | auto isVarToNormalize = [&](PTRef var) { 54 | return logic.isVar(var) and predicateVars.find(var) == predicateVars.end(); 55 | }; 56 | auto localVars = matchingSubTerms(logic, newInterpretedBody, isVarToNormalize); 57 | if (localVars.size() > 0) { 58 | // there are some local variables left, rename them and make them versioned 59 | TermUtils::substitutions_map subst; 60 | for (PTRef localVar : localVars) { 61 | SRef sort = logic.getSortRef(localVar); 62 | std::string uniq_name = "aux#" + std::to_string(counter++); 63 | PTRef renamed = timeMachine.getVarVersionZero(uniq_name, sort); 64 | subst.insert({localVar, renamed}); 65 | topLevelEqualities.push({.normalizedVar = renamed, .originalArg = localVar}); 66 | } 67 | newInterpretedBody = utils.varSubstitute(newInterpretedBody, subst); 68 | } 69 | return ChClause{clause.head, ChcBody{{newInterpretedBody}, clause.body.uninterpretedPart}}; 70 | } 71 | 72 | 73 | ChcHead Normalizer::normalize(ChcHead const& head) { 74 | auto predicate = head.predicate.predicate; 75 | auto predicateSymbol = logic.getSymRef(predicate); 76 | if (not canonicalPredicateRepresentation.hasRepresentationFor(predicateSymbol)) { 77 | createUniqueRepresentation(predicate); 78 | } 79 | assert(canonicalPredicateRepresentation.hasRepresentationFor(predicateSymbol)); 80 | PTRef targetTerm = canonicalPredicateRepresentation.getTargetTermFor(predicateSymbol); 81 | auto size = logic.getPterm(targetTerm).size(); 82 | assert(size == logic.getPterm(predicate).size()); 83 | for (int i = 0; i < size; ++i) { 84 | PTRef targetVar = logic.getPterm(targetTerm)[i]; 85 | PTRef arg = logic.getPterm(predicate)[i]; 86 | topLevelEqualities.push({.normalizedVar = targetVar, .originalArg = arg}); 87 | } 88 | return ChcHead{targetTerm}; 89 | } 90 | 91 | ChcBody Normalizer::normalize(const ChcBody & body) { 92 | // uninterpreted part 93 | std::vector newUninterpretedPart; 94 | auto const& uninterpreted = body.uninterpretedPart; 95 | auto proxy = canonicalPredicateRepresentation.createCountingProxy(); 96 | for (auto const& predicateWrapper : uninterpreted) { 97 | PTRef predicate = predicateWrapper.predicate; 98 | auto predicateSymbol = logic.getSymRef(predicate); 99 | if (not canonicalPredicateRepresentation.hasRepresentationFor(predicateSymbol)) { 100 | createUniqueRepresentation(predicate); 101 | } 102 | assert(canonicalPredicateRepresentation.hasRepresentationFor(predicateSymbol)); 103 | PTRef sourceTerm = proxy.getSourceTermFor(predicateSymbol); 104 | auto size = logic.getPterm(sourceTerm).size(); 105 | assert(size == logic.getPterm(predicate).size()); 106 | for (int i = 0; i < size; ++i) { 107 | PTRef arg = logic.getPterm(predicate)[i]; 108 | PTRef sourceVar = logic.getPterm(sourceTerm)[i]; 109 | topLevelEqualities.push({.normalizedVar = sourceVar, .originalArg = arg}); 110 | } 111 | newUninterpretedPart.push_back(UninterpretedPredicate{sourceTerm}); 112 | } 113 | // interpreted part 114 | PTRef newInterpretedPart = body.interpretedPart.fla; 115 | 116 | vec equalities; 117 | equalities.capacity(topLevelEqualities.size()); 118 | TermUtils::substitutions_map subst; 119 | for (auto [normalizedVar, originalArg] : topLevelEqualities) { 120 | if (logic.isVar(originalArg) and subst.find(originalArg) == subst.end()) { 121 | subst.insert({originalArg, normalizedVar}); 122 | } else { // originalArg is not variable or it has substitution assigned already 123 | // MB: We try to avoid creating equalities with original arguments, which would need to be rewritten later 124 | PTRef value = logic.isVar(originalArg) ? subst.at(originalArg) : originalArg; 125 | equalities.push(logic.mkEq(normalizedVar, value)); 126 | } 127 | } 128 | if (equalities.size() > 0) { 129 | newInterpretedPart = logic.mkAnd(newInterpretedPart, logic.mkAnd(std::move(equalities))); 130 | } 131 | newInterpretedPart = TermUtils(logic).varSubstitute(newInterpretedPart, subst); 132 | return ChcBody{InterpretedFla{newInterpretedPart}, std::move(newUninterpretedPart)}; 133 | } 134 | } // namespace golem 135 | 136 | 137 | -------------------------------------------------------------------------------- /src/Normalizer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_NORMALIZER_H 8 | #define GOLEM_NORMALIZER_H 9 | 10 | #include "ChcSystem.h" 11 | #include "TermUtils.h" 12 | 13 | #include 14 | 15 | namespace golem { 16 | struct NormalizedChcSystem{ 17 | std::unique_ptr normalizedSystem; 18 | NonlinearCanonicalPredicateRepresentation canonicalPredicateRepresentation; 19 | }; 20 | 21 | class Normalizer{ 22 | public: 23 | struct Equality { 24 | PTRef normalizedVar; 25 | PTRef originalArg; 26 | }; 27 | 28 | using Equalities = std::vector>; 29 | private: 30 | Logic& logic; 31 | TimeMachine timeMachine; 32 | NonlinearCanonicalPredicateRepresentation canonicalPredicateRepresentation; 33 | long long counter = 0; 34 | 35 | vec topLevelEqualities; 36 | Equalities normalizingEqualities; 37 | 38 | ChClause normalize(ChClause const & clause); 39 | 40 | ChcHead normalize(ChcHead const & head); 41 | 42 | ChcBody normalize(ChcBody const & body); 43 | 44 | void createUniqueRepresentation(PTRef predicate) { 45 | auto size = logic.getPterm(predicate).size(); 46 | std::vector repre; repre.reserve(size); 47 | for (int i = 0; i < size; ++i) { 48 | SRef sort = logic.getSortRef(logic.getPterm(predicate)[i]); 49 | std::string uniq_name = "x#" + std::to_string(counter++); 50 | PTRef versionlessVar = logic.mkVar(sort, uniq_name.c_str()); 51 | repre.push_back(versionlessVar); 52 | } 53 | canonicalPredicateRepresentation.addRepresentation(logic.getSymRef(predicate), std::move(repre)); 54 | } 55 | 56 | NonlinearCanonicalPredicateRepresentation getCanonicalPredicateRepresentation() const { 57 | return canonicalPredicateRepresentation; 58 | } 59 | 60 | ChClause eliminateRedundantVariables(ChClause && clause); 61 | 62 | ChClause normalizeAuxiliaryVariables(ChClause && clause); 63 | 64 | public: 65 | Normalizer(Logic& logic) : logic(logic), timeMachine(logic), canonicalPredicateRepresentation(logic) {} 66 | 67 | NormalizedChcSystem normalize(ChcSystem const & system); 68 | 69 | const vec & getNormalizingEqualities(std::size_t index) const { return std::move(normalizingEqualities.at(index)); } 70 | auto const & getNormalizingEqualities() const { return normalizingEqualities; }; 71 | 72 | }; 73 | } // namespace golem 74 | 75 | 76 | #endif // GOLEM_NORMALIZER_H 77 | -------------------------------------------------------------------------------- /src/Options.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_OPTIONS_H 8 | #define GOLEM_OPTIONS_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace golem { 15 | class Options { 16 | std::map options; 17 | 18 | public: 19 | void addOption(std::string key, std::string value) { options.emplace(std::move(key), std::move(value)); } 20 | 21 | [[nodiscard]] std::optional getOption(std::string const & key) const { 22 | auto it = options.find(key); 23 | return it == options.end() ? std::nullopt : std::optional(it->second); 24 | } 25 | 26 | [[nodiscard]] std::string getOrDefault(std::string const & key, std::string const & def) const { 27 | auto it = options.find(key); 28 | return it == options.end() ? def : it->second; 29 | } 30 | 31 | [[nodiscard]] bool hasOption(std::string const & key) const { 32 | auto it = options.find(key); 33 | return it != options.end(); 34 | } 35 | 36 | static const std::string INPUT_FILE; 37 | static const std::string LOGIC; 38 | static const std::string ENGINE; 39 | static const std::string ANALYSIS_FLOW; 40 | static const std::string VALIDATE_RESULT; 41 | static const std::string COMPUTE_WITNESS; 42 | static const std::string PRINT_WITNESS; 43 | static const std::string PROOF_FORMAT; 44 | static const std::string LRA_ITP_ALG; 45 | static const std::string FORCED_COVERING; 46 | static const std::string VERBOSE; 47 | static const std::string TPA_USE_QE; 48 | static const std::string FORCE_TS; 49 | static const std::string SIMPLIFY_NESTED; 50 | }; 51 | 52 | class CommandLineParser { 53 | public: 54 | Options parse(int argc, char * argv[]); 55 | }; 56 | } // namespace golem 57 | #endif // GOLEM_OPTIONS_H 58 | -------------------------------------------------------------------------------- /src/QuantifierElimination.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "QuantifierElimination.h" 8 | 9 | #include "ModelBasedProjection.h" 10 | #include "TermUtils.h" 11 | #include "utils/SmtSolver.h" 12 | 13 | namespace { 14 | using namespace golem; 15 | PTRef eliminate(Logic & logic, PTRef fla, vec const & vars) { 16 | vec projections; 17 | 18 | SMTSolver solver(logic, SMTSolver::WitnessProduction::ONLY_MODEL); 19 | solver.assertProp(fla); 20 | while (true) { 21 | auto res = solver.check(); 22 | if (res == SMTSolver::Answer::UNSAT) { break; } 23 | if (res == SMTSolver::Answer::SAT) { 24 | auto model = solver.getModel(); 25 | ModelBasedProjection mbp(logic); 26 | PTRef projection = mbp.project(fla, vars, *model); 27 | projections.push(projection); 28 | solver.push(); // to avoid processing the same formula over and over again 29 | solver.assertProp(logic.mkNot(projection)); 30 | } else { 31 | throw std::logic_error("Error in solver during quantifier elimination"); 32 | } 33 | } 34 | PTRef result = logic.mkOr(projections); 35 | if (logic.isBooleanOperator(result) and not logic.isNot(result)) { 36 | result = ::rewriteMaxArityAggresive(logic, result); 37 | result = ::simplifyUnderAssignment_Aggressive(result, logic); 38 | // TODO: more simplifications? 39 | } 40 | return result; 41 | } 42 | } // namespace 43 | 44 | namespace golem { 45 | PTRef QuantifierElimination::keepOnly(PTRef fla, const vec & varsToKeep) { 46 | auto allVars = TermUtils(logic).getVars(fla); 47 | vec toEliminate; 48 | for (PTRef var : allVars) { 49 | if (std::find(varsToKeep.begin(), varsToKeep.end(), var) == varsToKeep.end()) { toEliminate.push(var); } 50 | } 51 | return eliminate(fla, toEliminate); 52 | } 53 | 54 | PTRef QuantifierElimination::eliminate(PTRef fla, PTRef var) { 55 | return eliminate(fla, vec{var}); 56 | } 57 | 58 | PTRef QuantifierElimination::eliminate(PTRef fla, vec const & vars) { 59 | if (not std::all_of(vars.begin(), vars.end(), [this](PTRef var) { return logic.isVar(var); }) or 60 | not logic.hasSortBool(fla)) { 61 | throw std::invalid_argument("Invalid arguments to quantifier elimination"); 62 | } 63 | 64 | fla = TermUtils(logic).toNNF(fla); 65 | return ::eliminate(logic, fla, vars); 66 | } 67 | } // namespace golem 68 | -------------------------------------------------------------------------------- /src/QuantifierElimination.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef OPENSMT_QUANTIFIERELIMINATION_H 8 | #define OPENSMT_QUANTIFIERELIMINATION_H 9 | 10 | #include "osmt_terms.h" 11 | 12 | namespace golem { 13 | /* 14 | * A utility for precise elimination of (existential) quantifiers from a formula. 15 | * 16 | * Given a formula F(x,y) we want to compute a formula G(x) such that G(x) \equiv \exist y F(x,y) 17 | */ 18 | class QuantifierElimination { 19 | public: 20 | explicit QuantifierElimination(Logic & logic) : logic(logic) {} 21 | 22 | PTRef eliminate(PTRef fla, PTRef var); 23 | PTRef eliminate(PTRef fla, vec const & vars); 24 | PTRef keepOnly(PTRef, vec const & vars); 25 | 26 | private: 27 | Logic & logic; 28 | }; 29 | } // namespace golem 30 | 31 | #endif // OPENSMT_QUANTIFIERELIMINATION_H 32 | -------------------------------------------------------------------------------- /src/TransformationUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_TRANSFORMATIONUTILS_H 8 | #define GOLEM_TRANSFORMATIONUTILS_H 9 | 10 | #include "TransitionSystem.h" 11 | #include "Witnesses.h" 12 | #include "graph/ChcGraph.h" 13 | 14 | #include 15 | 16 | namespace golem { 17 | bool isTransitionSystem(ChcDirectedGraph const & graph); 18 | 19 | bool isTransitionSystemDAG(ChcDirectedGraph const & graph); 20 | 21 | bool isTrivial(ChcDirectedGraph const & graph); 22 | 23 | std::unique_ptr toTransitionSystem(ChcDirectedGraph const & graph); 24 | 25 | std::unique_ptr ensureNoAuxiliaryVariablesInInitAndQuery(std::unique_ptr ts); 26 | 27 | struct EdgeVariables { 28 | std::vector stateVars; 29 | std::vector nextStateVars; 30 | std::vector auxiliaryVars; 31 | }; 32 | 33 | EdgeVariables getVariablesFromEdge(Logic & logic, ChcDirectedGraph const & graph, EId eid); 34 | 35 | std::unique_ptr systemTypeFrom(vec const & stateVars, vec const & auxVars, Logic & logic); 36 | 37 | PTRef transitionFormulaInSystemType(SystemType const & systemType, EdgeVariables const & edgeVariables, PTRef edgeLabel, 38 | Logic & logic); 39 | 40 | std::vector detectLoop(const ChcDirectedGraph & graph); 41 | } // namespace golem 42 | 43 | #endif // GOLEM_TRANSFORMATIONUTILS_H 44 | -------------------------------------------------------------------------------- /src/TransitionSystem.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_TRANSITIONSYSTEM_H 8 | #define GOLEM_TRANSITIONSYSTEM_H 9 | 10 | #include "osmt_terms.h" 11 | 12 | #include 13 | #include 14 | 15 | namespace golem { 16 | class SystemType { 17 | 18 | std::vector stateVars; 19 | std::vector nextStateVars; 20 | std::vector auxiliaryVars; // Allowed in transition relation, but also in the initital states or bad states 21 | 22 | Logic & logic; 23 | 24 | public: 25 | SystemType(std::vector stateVarTypes, std::vector auxiliaryVarTypes, Logic & logic); 26 | SystemType(std::vector stateVars, std::vector auxiliaryVars, Logic & logic); 27 | SystemType(vec const & stateVars, vec const & auxiliaryVars, Logic & logic); 28 | 29 | [[nodiscard]] bool isStateFormula(PTRef fla) const; 30 | 31 | [[nodiscard]] bool isTransitionFormula(PTRef fla) const; 32 | 33 | [[nodiscard]] std::vector const & getStateVars() const { return stateVars; } 34 | [[nodiscard]] std::vector const & getNextStateVars() const { return nextStateVars; } 35 | [[nodiscard]] std::vector const & getAuxiliaryVars() const { return auxiliaryVars; } 36 | }; 37 | 38 | class TransitionSystem { 39 | 40 | Logic & logic; 41 | 42 | std::unique_ptr systemType; 43 | 44 | PTRef init; 45 | PTRef transition; 46 | PTRef query; 47 | 48 | public: 49 | TransitionSystem(Logic & logic, std::unique_ptr systemType, PTRef initialStates, 50 | PTRef transitionRelation, PTRef badStates) 51 | : logic(logic), 52 | systemType(std::move(systemType)), 53 | init(initialStates), 54 | transition(transitionRelation), 55 | query(badStates) { 56 | if (not isWellFormed()) { throw std::logic_error("Transition system not created correctly"); } 57 | } 58 | 59 | [[nodiscard]] PTRef getInit() const; 60 | [[nodiscard]] PTRef getQuery() const; 61 | [[nodiscard]] PTRef getTransition() const; 62 | 63 | [[nodiscard]] Logic & getLogic() const; 64 | 65 | [[nodiscard]] std::vector getStateVars() const; 66 | [[nodiscard]] std::vector getNextStateVars() const; 67 | [[nodiscard]] std::vector getAuxiliaryVars() const; 68 | 69 | static TransitionSystem reverse(TransitionSystem const &); 70 | 71 | static PTRef reverseTransitionRelation(TransitionSystem const &); 72 | 73 | private: 74 | [[nodiscard]] bool isWellFormed() const; 75 | 76 | [[nodiscard]] PTRef toNextStateVar(PTRef var) const; 77 | }; 78 | 79 | struct KTo1Inductive { 80 | enum class Mode { UNFOLD, QE }; 81 | explicit KTo1Inductive(Mode mode) : mode(mode) {} 82 | [[nodiscard]] PTRef kinductiveToInductive(PTRef invariant, unsigned k, TransitionSystem const & system) const; 83 | 84 | private: 85 | Mode mode; 86 | 87 | [[nodiscard]] static PTRef qe(PTRef invariant, unsigned k, TransitionSystem const & system); 88 | [[nodiscard]] static PTRef unfold(PTRef invariant, unsigned k, TransitionSystem const & system); 89 | }; 90 | 91 | PTRef kinductiveToInductive(PTRef invariant, unsigned k, TransitionSystem const & system); 92 | } // namespace golem 93 | 94 | #endif // GOLEM_TRANSITIONSYSTEM_H 95 | -------------------------------------------------------------------------------- /src/Validator.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "Validator.h" 8 | 9 | #include "utils/SmtSolver.h" 10 | 11 | namespace golem { 12 | Validator::Result Validator::validate(ChcDirectedHyperGraph const & graph, VerificationResult const & result) { 13 | if (not result.hasWitness()) { return Validator::Result::NOT_VALIDATED; } 14 | switch (result.getAnswer()) { 15 | case VerificationAnswer::SAFE: 16 | return validateValidityWitness(graph, result.getValidityWitness()); 17 | case VerificationAnswer::UNSAFE: 18 | return validateInvalidityWitness(graph, result.getInvalidityWitness()); 19 | default: 20 | return Validator::Result::NOT_VALIDATED; 21 | } 22 | return Validator::Result::NOT_VALIDATED; 23 | } 24 | 25 | Validator::Result Validator::validateValidityWitness(ChcDirectedHyperGraph const & graph, ValidityWitness const & witness) const { 26 | auto definitions = witness.getDefinitions(); 27 | assert(definitions.find(graph.getEntry()) != definitions.end()); 28 | assert(definitions.find(graph.getExit()) != definitions.end()); 29 | // get correct interpretation for each node 30 | auto getInterpretation = [&](SymRef symbol) -> PTRef { 31 | auto const it = definitions.find(symbol); 32 | if (it == definitions.end()) { 33 | std::cerr << ";Missing definition of a predicate " << logic.printSym(symbol) << std::endl; 34 | return PTRef_Undef; 35 | } 36 | return it->second; 37 | }; 38 | 39 | ChcDirectedHyperGraph::VertexInstances vertexInstances(graph); 40 | VersionManager versionManager(logic); 41 | auto edges = graph.getEdges(); 42 | for (auto const & edge : edges) { 43 | vec bodyComponents; 44 | PTRef constraint = edge.fla.fla; 45 | bodyComponents.push(constraint); 46 | for (std::size_t i = 0; i < edge.from.size(); ++i) { 47 | auto source = edge.from[i]; 48 | PTRef interpretation = getInterpretation(source); 49 | if (interpretation == PTRef_Undef) { return Result::NOT_VALIDATED; } 50 | PTRef versioned = versionManager.baseFormulaToSource(interpretation, vertexInstances.getInstanceNumber(edge.id, i)); 51 | bodyComponents.push(versioned); 52 | } 53 | PTRef interpretedBody = logic.mkAnd(std::move(bodyComponents)); 54 | PTRef interpretedHead = getInterpretation(edge.to); 55 | if (interpretedHead == PTRef_Undef) { return Result::NOT_VALIDATED; } 56 | PTRef versionedInterpretedHead = versionManager.baseFormulaToTarget(interpretedHead); 57 | PTRef query = logic.mkAnd(interpretedBody, logic.mkNot(versionedInterpretedHead)); 58 | { 59 | SMTSolver solver(logic, SMTSolver::WitnessProduction::NONE); 60 | solver.assertProp(query); 61 | auto res = solver.check(); 62 | if (res != SMTSolver::Answer::UNSAT) { 63 | std::cerr << ";Edge not validated!"; 64 | // TODO: print edge 65 | return Result::NOT_VALIDATED; 66 | } 67 | } 68 | } 69 | return Result::VALIDATED; 70 | } 71 | 72 | 73 | Validator::Result validateStep( 74 | std::size_t stepIndex, 75 | InvalidityWitness::Derivation const & derivation, 76 | ChcDirectedHyperGraph const & graph, 77 | ChcDirectedHyperGraph::VertexInstances const & vertexInstances 78 | ) { 79 | Logic & logic = graph.getLogic(); 80 | auto const & step = derivation[stepIndex]; 81 | EId edge = step.clauseId; 82 | TermUtils::substitutions_map subst; 83 | TermUtils utils(logic); 84 | auto fillVariables = [&](PTRef derivedFact, PTRef normalizedPredicate) { 85 | assert(logic.getSymRef(derivedFact) == logic.getSymRef(normalizedPredicate)); 86 | auto const & term = logic.getPterm(derivedFact); (void) term; 87 | assert(std::all_of(term.begin(), term.end(), [&](PTRef arg) { return logic.isConstant(arg); })); 88 | utils.mapFromPredicate(normalizedPredicate, derivedFact, subst); 89 | }; 90 | // get values for the variables from predicates 91 | auto const & sourceNodes = graph.getSources(edge); 92 | assert(sourceNodes.size() == step.premises.size()); 93 | for (std::size_t i = 0; i < sourceNodes.size(); ++i) { 94 | auto source = sourceNodes[i]; 95 | auto const & premise = derivation[step.premises[i]]; 96 | fillVariables(premise.derivedFact, graph.getStateVersion(source, vertexInstances.getInstanceNumber(edge, i))); 97 | } 98 | auto target = graph.getTarget(edge); 99 | fillVariables(step.derivedFact, graph.getNextStateVersion(target)); 100 | PTRef constraintAfterSubstitution = utils.varSubstitute(graph.getEdgeLabel(edge), subst); 101 | if (constraintAfterSubstitution == logic.getTerm_true()) { return Validator::Result::VALIDATED; } 102 | if (constraintAfterSubstitution == logic.getTerm_false()) { return Validator::Result::NOT_VALIDATED; } 103 | SMTSolver solver(logic, SMTSolver::WitnessProduction::NONE); 104 | solver.assertProp(constraintAfterSubstitution); 105 | auto res = solver.check(); 106 | if (res == SMTSolver::Answer::SAT) { return Validator::Result::VALIDATED; } 107 | return Validator::Result::NOT_VALIDATED; 108 | } 109 | 110 | Validator::Result 111 | Validator::validateInvalidityWitness(ChcDirectedHyperGraph const & graph, InvalidityWitness const & witness) const { 112 | auto const & derivation = witness.getDerivation(); 113 | auto derivationSize = derivation.size(); 114 | if (derivationSize == 0) { return Result::NOT_VALIDATED; } 115 | ChcDirectedHyperGraph::VertexInstances vertexInstances(graph); 116 | if (derivation[0].derivedFact != logic.getTerm_true()) { 117 | assert(false); 118 | std::cerr << "; Validator: Invalidity witness does not start with TRUE!\n"; 119 | return Result::NOT_VALIDATED; 120 | } 121 | if (derivation.last().derivedFact != logic.getTerm_false()) { 122 | std::cerr << "; Validator: Root of the invalidity witness is not FALSE!\n"; 123 | return Result::NOT_VALIDATED; 124 | } 125 | for (std::size_t i = 1; i < derivationSize; ++i) { 126 | auto result = validateStep(i, derivation, graph, vertexInstances); 127 | if (result == Validator::Result::NOT_VALIDATED) { return result; } 128 | } 129 | return Validator::Result::VALIDATED; 130 | } 131 | } // namespace golem 132 | -------------------------------------------------------------------------------- /src/Validator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_VALIDATOR_H 8 | #define GOLEM_VALIDATOR_H 9 | 10 | #include "Witnesses.h" 11 | 12 | #include "graph/ChcGraph.h" 13 | 14 | namespace golem { 15 | struct ValidationException : public std::runtime_error { 16 | explicit ValidationException(const std::string & msg) : std::runtime_error(msg) {} 17 | explicit ValidationException(const char * msg) : std::runtime_error(msg) {} 18 | }; 19 | 20 | class Validator { 21 | Logic & logic; 22 | public: 23 | explicit Validator(Logic & logic) : logic(logic) {} 24 | 25 | enum class Result {VALIDATED, NOT_VALIDATED}; 26 | Result validate(ChcDirectedHyperGraph const & system, VerificationResult const & result); 27 | 28 | private: 29 | [[nodiscard]] 30 | Result validateValidityWitness(ChcDirectedHyperGraph const & graph, ValidityWitness const & witness) const; 31 | 32 | [[nodiscard]] 33 | Result validateInvalidityWitness(ChcDirectedHyperGraph const & graph, InvalidityWitness const & witness) const; 34 | }; 35 | } // namespace golem 36 | 37 | 38 | #endif //GOLEM_VALIDATOR_H 39 | -------------------------------------------------------------------------------- /src/bin/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(Golem "golem.cpp") 2 | 3 | target_link_libraries(Golem PRIVATE golem_lib) 4 | 5 | set_target_properties(Golem PROPERTIES 6 | OUTPUT_NAME golem 7 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") 8 | -------------------------------------------------------------------------------- /src/bin/golem.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "ChcInterpreter.h" 8 | #include "Options.h" 9 | 10 | #include "osmt_terms.h" 11 | #include "osmt_parser.h" 12 | 13 | namespace { 14 | std::string tryDetectLogic(ASTNode const * root) { 15 | if (not root or not root->children) { return ""; } 16 | auto const & children = *(root->children); 17 | bool hasReals = false; 18 | bool hasIntegers = false; 19 | bool hasArrays = false; 20 | auto decide = [&]() -> std::string { 21 | if (hasReals and hasIntegers) { return ""; } 22 | return std::string("QF_") + (hasArrays ? "A" : "") + "L" + (hasIntegers ? "I" : "R") + "A"; 23 | }; 24 | auto checkForRealsAndInts = [&](ASTNode const * const node) { 25 | hasReals = hasReals or strcmp(node->getValue(), "Real") == 0; 26 | hasIntegers = hasIntegers or strcmp(node->getValue(), "Int") == 0; 27 | }; 28 | for (ASTNode * child : children) { 29 | const tokens::smt2token token = child->getToken(); 30 | switch (token.x) { 31 | case tokens::t_declarefun: 32 | { 33 | auto it = child->children->begin(); 34 | ASTNode const & name_node = **(it++); (void)name_node; 35 | ASTNode const & args_node = **(it++); 36 | ASTNode const & ret_node = **(it++); (void)ret_node; 37 | assert(it == child->children->end()); 38 | for (ASTNode const * const argNode : *(args_node.children)) { 39 | if (argNode->getType() == SYM_T) { 40 | checkForRealsAndInts(argNode); 41 | } else if (argNode->getType() == LID_T and argNode->children) { 42 | for (ASTNode const * const node : *(argNode->children)) { 43 | if (node->getType() == SYM_T) { 44 | hasArrays = hasArrays or strcmp(node->getValue(), "Array") == 0; 45 | checkForRealsAndInts(node); 46 | } 47 | } 48 | } 49 | } 50 | break; 51 | } 52 | case tokens::t_assert: 53 | { 54 | if (hasIntegers or hasReals) { return decide(); } 55 | // check the sorts of quantified variables 56 | auto it = child->children->begin(); 57 | if ((**it).getType() == FORALL_T) { 58 | ASTNode const * qvars = *(**it).children->begin(); 59 | assert(qvars and qvars->getType() == SVL_T); 60 | for (ASTNode const * qvar : *qvars->children) { 61 | assert(qvar and qvar->getType() == SV_T); 62 | ASTNode const * sortNode = *qvar->children->begin(); 63 | checkForRealsAndInts(sortNode); 64 | } 65 | } 66 | break; 67 | } 68 | default: 69 | ; 70 | } 71 | } 72 | return decide(); 73 | } 74 | 75 | void error(std::string const & msg) { 76 | std::cerr << msg << '\n'; 77 | exit(1); 78 | } 79 | } // namespace 80 | 81 | using namespace golem; 82 | 83 | int main( int argc, char * argv[] ) { 84 | SMTConfig c; 85 | 86 | CommandLineParser parser; 87 | auto options = parser.parse(argc, argv); 88 | auto inputFile = options.getOrDefault(Options::INPUT_FILE, ""); 89 | auto logicFromString = [](std::string const & logic_str) -> std::unique_ptr { 90 | if (logic_str == std::string("QF_LRA")) { 91 | return std::make_unique(opensmt::Logic_t::QF_LRA); 92 | } else if (logic_str == std::string("QF_LIA")) { 93 | return std::make_unique(opensmt::Logic_t::QF_LIA); 94 | } else if (logic_str == std::string("QF_ALIA")) { 95 | return std::make_unique(opensmt::Logic_t::QF_ALIA); 96 | } else { 97 | error("Unknown logic specified: " + logic_str); 98 | exit(1); 99 | } 100 | }; 101 | if (inputFile.empty()) { 102 | error("No input file provided"); 103 | } 104 | { 105 | FILE * fin = nullptr; 106 | // check the file 107 | const char * filename = inputFile.c_str(); 108 | assert(filename); 109 | 110 | if ((fin = fopen(filename, "rt")) == nullptr) { 111 | error("can't open file"); 112 | } 113 | 114 | const char * extension = strrchr( filename, '.' ); 115 | if (extension != nullptr && strcmp(extension, ".smt2") == 0) { 116 | Smt2newContext context(fin); 117 | int rval = osmt_yyparse(&context); 118 | if (rval != 0) { 119 | fclose(fin); 120 | error("Error when parsing input file"); 121 | } 122 | auto logicStr = options.hasOption(Options::LOGIC) ? options.getOption(Options::LOGIC).value() : tryDetectLogic(context.getRoot()); 123 | auto logic = logicFromString(logicStr); 124 | ChcInterpreter interpreter(options); 125 | interpreter.interpretSystemAst(*logic, context.getRoot()); 126 | fclose(fin); 127 | } 128 | else { 129 | fclose(fin); 130 | error(inputFile + " extension not recognized. File must be in smt-lib2 format (extension .smt2)"); 131 | } 132 | } 133 | return 0; 134 | } 135 | 136 | -------------------------------------------------------------------------------- /src/engine/ArgBasedEngine.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_ARGBASEDENGINE_H 8 | #define GOLEM_ARGBASEDENGINE_H 9 | 10 | #include "Engine.h" 11 | 12 | #include "Options.h" 13 | #include "Witnesses.h" 14 | #include "graph/ChcGraph.h" 15 | 16 | #include "osmt_terms.h" 17 | 18 | namespace golem { 19 | class ARGBasedEngine : public Engine { 20 | Options const & options; 21 | 22 | public: 23 | ARGBasedEngine(Logic &, Options const & options) : options(options) {} 24 | 25 | VerificationResult solve(ChcDirectedHyperGraph const & graph) override; 26 | }; 27 | } // namespace golem 28 | 29 | #endif // GOLEM_ARGBASEDENGINE_H 30 | -------------------------------------------------------------------------------- /src/engine/Bmc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_BMC_H 8 | #define GOLEM_BMC_H 9 | 10 | #include "Engine.h" 11 | #include "Options.h" 12 | #include "TransitionSystem.h" 13 | 14 | namespace golem { 15 | class BMC : public Engine { 16 | Logic & logic; 17 | // Options const & options; 18 | bool needsWitness = false; 19 | int verbosity = 0; 20 | bool forceTransitionSystem = true; 21 | 22 | public: 23 | BMC(Logic & logic, Options const & options) : logic(logic) { 24 | needsWitness = options.getOrDefault(Options::COMPUTE_WITNESS, "") == "true"; 25 | verbosity = std::stoi(options.getOrDefault(Options::VERBOSE, "0")); 26 | forceTransitionSystem = options.getOrDefault(Options::FORCE_TS, "") == "true"; 27 | } 28 | 29 | VerificationResult solve(ChcDirectedHyperGraph const & graph) override { 30 | if (graph.isNormalGraph()) { 31 | auto normalGraph = graph.toNormalGraph(); 32 | return solve(*normalGraph); 33 | } 34 | return VerificationResult(VerificationAnswer::UNKNOWN); 35 | } 36 | 37 | VerificationResult solve(ChcDirectedGraph const & graph); 38 | 39 | private: 40 | VerificationResult solveTransitionSystem(ChcDirectedGraph const & graph); 41 | TransitionSystemVerificationResult solveTransitionSystemInternal(TransitionSystem const & system); 42 | VerificationResult solveGeneralLinearSystem(ChcDirectedGraph const & graph); 43 | }; 44 | } // namespace golem 45 | 46 | #endif // GOLEM_BMC_H 47 | -------------------------------------------------------------------------------- /src/engine/Common.cc: -------------------------------------------------------------------------------- 1 | #include "Common.h" 2 | 3 | #include "utils/SmtSolver.h" 4 | 5 | namespace golem { 6 | VerificationResult solveTrivial(ChcDirectedGraph const & graph) { 7 | Logic & logic = graph.getLogic(); 8 | // All edges should be between entry and exit, check if any of them has a satisfiable label 9 | auto edgeIds = graph.getEdges(); 10 | for (EId eid : edgeIds) { 11 | assert(graph.getSource(eid) == graph.getEntry()); 12 | assert(graph.getTarget(eid) == graph.getExit()); 13 | PTRef label = graph.getEdgeLabel(eid); 14 | if (label == logic.getTerm_false()) { continue; } 15 | SMTSolver solver(logic, SMTSolver::WitnessProduction::NONE); 16 | solver.assertProp(label); 17 | auto res = solver.check(); 18 | if (res == SMTSolver::Answer::UNSAT) { 19 | continue; 20 | } else if (res == SMTSolver::Answer::SAT) { 21 | InvalidityWitness::Derivation derivation; 22 | derivation.addDerivationStep( 23 | {.index = 0, .premises = {}, .derivedFact = logic.getTerm_true(), .clauseId = {static_cast(-1)}}); 24 | derivation.addDerivationStep( 25 | {.index = 1, .premises = {0}, .derivedFact = logic.getTerm_false(), .clauseId = eid}); 26 | InvalidityWitness witness; 27 | witness.setDerivation(std::move(derivation)); 28 | return {VerificationAnswer::UNSAFE, std::move(witness)}; 29 | } else { 30 | // Unexpected solver result; 31 | return VerificationResult{VerificationAnswer::UNKNOWN}; 32 | } 33 | } 34 | // Here we know that no edge is satisfiable 35 | return {VerificationAnswer::SAFE, ValidityWitness::trivialWitness(graph)}; 36 | } 37 | } // namespace golem 38 | -------------------------------------------------------------------------------- /src/engine/Common.h: -------------------------------------------------------------------------------- 1 | #ifndef GOLEM_COMMON_H 2 | #define GOLEM_COMMON_H 3 | 4 | #include "Witnesses.h" 5 | #include "graph/ChcGraph.h" 6 | 7 | namespace golem { 8 | 9 | VerificationResult solveTrivial(ChcDirectedGraph const & graph); 10 | 11 | } // namespace golem 12 | 13 | #endif // GOLEM_COMMON_H 14 | -------------------------------------------------------------------------------- /src/engine/DAR.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef DAR_H 8 | #define DAR_H 9 | 10 | #include "Options.h" 11 | #include "TransitionSystemEngine.h" 12 | 13 | namespace golem { 14 | class DAR : public TransitionSystemEngine { 15 | Logic & logic; 16 | 17 | public: 18 | DAR(Logic & logic, Options const & options) : logic(logic) { 19 | computeWitness = options.getOrDefault(Options::COMPUTE_WITNESS, "") == "true"; 20 | } 21 | 22 | private: 23 | TransitionSystemVerificationResult solve(TransitionSystem const & system) override; 24 | }; 25 | } // namespace golem 26 | 27 | #endif // DAR_H -------------------------------------------------------------------------------- /src/engine/Engine.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_ENGINE_H 8 | #define GOLEM_ENGINE_H 9 | 10 | #include "Witnesses.h" 11 | #include "graph/ChcGraph.h" 12 | 13 | namespace golem { 14 | class Engine { 15 | public: 16 | virtual VerificationResult solve(ChcDirectedHyperGraph const &) { 17 | return VerificationResult(VerificationAnswer::UNKNOWN); 18 | } 19 | 20 | virtual ~Engine() = default; 21 | }; 22 | } // namespace golem 23 | 24 | #endif //GOLEM_ENGINE_H 25 | -------------------------------------------------------------------------------- /src/engine/EngineFactory.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "EngineFactory.h" 8 | 9 | #include "ArgBasedEngine.h" 10 | #include "Bmc.h" 11 | #include "DAR.h" 12 | #include "IMC.h" 13 | #include "Kind.h" 14 | #include "Lawi.h" 15 | #include "PDKind.h" 16 | #include "Spacer.h" 17 | #include "SymbolicExecution.h" 18 | #include "TPA.h" 19 | 20 | namespace golem { 21 | std::unique_ptr EngineFactory::getEngine(std::string_view engine) && { 22 | if (engine == "spacer") { 23 | return std::make_unique(logic, options); 24 | } else if (engine == TPAEngine::TPA) { 25 | return std::make_unique(logic, options, TPACore::BASIC); 26 | } else if (engine == TPAEngine::SPLIT_TPA) { 27 | return std::make_unique(logic, options, TPACore::SPLIT); 28 | } else if (engine == "bmc") { 29 | return std::make_unique(logic, options); 30 | } else if (engine == "dar") { 31 | return std::make_unique(logic, options); 32 | } else if (engine == "lawi") { 33 | return std::make_unique(logic, options); 34 | } else if (engine == "kind") { 35 | return std::make_unique(logic, options); 36 | } else if (engine == "imc") { 37 | return std::make_unique(logic, options); 38 | } else if (engine == "pdkind") { 39 | return std::make_unique(logic, options); 40 | } else if (engine == "pa") { 41 | return std::make_unique(logic, options); 42 | } else if (engine == "se") { 43 | return std::make_unique(logic, options); 44 | } else { 45 | throw std::invalid_argument("Unknown engine specified"); 46 | } 47 | } 48 | } // namespace golem -------------------------------------------------------------------------------- /src/engine/EngineFactory.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_ENGINEFACTORY_H 8 | #define GOLEM_ENGINEFACTORY_H 9 | 10 | #include "Engine.h" 11 | #include "Options.h" 12 | 13 | namespace golem { 14 | class EngineFactory { 15 | public: 16 | EngineFactory(Logic & logic, Options const & options) : logic(logic), options(options) {} 17 | std::unique_ptr getEngine(std::string_view engine) &&; 18 | 19 | private: 20 | Logic & logic; 21 | Options const & options; 22 | }; 23 | } // namespace golem 24 | 25 | #endif // GOLEM_ENGINEFACTORY_H 26 | -------------------------------------------------------------------------------- /src/engine/IMC.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Konstantin Britikov 3 | * Copyright (c) 2023-2025, Martin Blicha 4 | * 5 | * SPDX-License-Identifier: MIT 6 | */ 7 | 8 | #include "IMC.h" 9 | 10 | #include "Common.h" 11 | #include "TermUtils.h" 12 | #include "TransformationUtils.h" 13 | #include "transformers/SingleLoopTransformation.h" 14 | #include "utils/SmtSolver.h" 15 | 16 | namespace golem { 17 | TransitionSystemVerificationResult IMC::solve(TransitionSystem const & system) { 18 | { // if I /\ F is Satisfiable, return true 19 | SMTSolver solver(logic, SMTSolver::WitnessProduction::NONE); 20 | solver.assertProp(system.getInit()); 21 | solver.assertProp(system.getQuery()); 22 | if (solver.check() == SMTSolver::Answer::SAT) { 23 | return TransitionSystemVerificationResult{VerificationAnswer::UNSAFE, 0u}; 24 | } 25 | } 26 | if (logic.hasArrays()) { return TransitionSystemVerificationResult{VerificationAnswer::UNKNOWN, 0u}; } 27 | std::size_t maxLoopUnrollings = std::numeric_limits::max(); 28 | for (uint32_t k = 1; k < maxLoopUnrollings; ++k) { 29 | auto res = finiteRun(system, k); 30 | if (res.answer != VerificationAnswer::UNKNOWN) { return res; } 31 | } 32 | return TransitionSystemVerificationResult{VerificationAnswer::UNKNOWN, 0u}; 33 | } 34 | 35 | namespace { // Helper method for IMC::finiteRun 36 | PTRef getInterpolant(SMTSolver & solver, ipartitions_t const & mask) { 37 | auto itpContext = solver.getInterpolationContext(); 38 | vec itps; 39 | itpContext->getSingleInterpolant(itps, mask); 40 | assert(itps.size() == 1); 41 | return itps[0]; 42 | } 43 | } // namespace 44 | 45 | // procedure FiniteRun(M=(I,T,F), k>0) 46 | TransitionSystemVerificationResult IMC::finiteRun(TransitionSystem const & ts, unsigned k) { 47 | if (verbosity > 0) { std::cout << "[IMC] Checking with lookahead length " << k << '\n'; } 48 | assert(k > 0); 49 | SMTSolver solver(logic, SMTSolver::WitnessProduction::ONLY_INTERPOLANTS); 50 | solver.getConfig().setSimplifyInterpolant(4); 51 | TimeMachine tm{logic}; 52 | PTRef suffix = [&]() { 53 | int lookahead = static_cast(k); 54 | vec suffixTransitions; 55 | for (auto i = 1; i < lookahead; ++i) { 56 | suffixTransitions.push(tm.sendFlaThroughTime(ts.getTransition(), i)); 57 | } 58 | suffixTransitions.push(tm.sendFlaThroughTime(ts.getQuery(), lookahead)); 59 | return logic.mkAnd(std::move(suffixTransitions)); 60 | }(); 61 | solver.assertProp(suffix); 62 | solver.assertProp(ts.getTransition()); // first step, part of prefix 63 | PTRef movingInit = ts.getInit(); 64 | PTRef reachedStates = ts.getInit(); 65 | unsigned iter = 0; 66 | while (true) { 67 | solver.push(); 68 | solver.assertProp(movingInit); // new initial states 69 | auto res = solver.check(); 70 | // if prefix + suffix is satisfiable 71 | if (res == SMTSolver::Answer::SAT) { 72 | if (movingInit == ts.getInit()) { 73 | // Real counterexample 74 | return {VerificationAnswer::UNSAFE, iter + k}; 75 | } else { 76 | // Possibly spurious counterexample => Abort and continue with larger k 77 | return {VerificationAnswer::UNKNOWN, PTRef_Undef}; 78 | } 79 | } else { // if prefix + suffix is unsatisfiable 80 | ipartitions_t mask = 0; 81 | opensmt::setbit(mask, 1); // for the prefix part of the TR 82 | opensmt::setbit(mask, iter + 2); // there are two formulas inserted at the base level: suffix + TR of prefix 83 | // let P = Itp(P, A, B) 84 | // let R' = P 85 | PTRef itp = getInterpolant(solver, mask); 86 | itp = tm.sendFlaThroughTime(itp, -1); 87 | // if R' => R return False(if R' /\ not R returns True) 88 | if (implies(itp, reachedStates)) { 89 | if (not computeWitness) { return {VerificationAnswer::SAFE, PTRef_Undef}; } 90 | PTRef inductiveInvariant = reachedStates; 91 | PTRef finalInductiveInvariant = computeFinalInductiveInvariant(inductiveInvariant, k, ts); 92 | return {VerificationAnswer::SAFE, finalInductiveInvariant}; 93 | } 94 | movingInit = itp; 95 | reachedStates = logic.mkOr(reachedStates, itp); 96 | } 97 | iter++; 98 | solver.pop(); 99 | } 100 | } 101 | 102 | bool IMC::implies(PTRef antecedent, PTRef consequent) const { 103 | SMTSolver solver(logic, SMTSolver::WitnessProduction::NONE); 104 | PTRef negated = logic.mkAnd(antecedent, logic.mkNot(consequent)); 105 | solver.assertProp(negated); 106 | auto res = solver.check(); 107 | return res == SMTSolver::Answer::UNSAT; 108 | } 109 | 110 | /** 111 | * In our current implementation, we find an inductive invariant that is not necessarily safe, we only know that it 112 | * cannot reach Bad in k steps. However, we also know that Bad cannot be truly reachable, otherwise the path would have 113 | * been discovered earlier. 114 | * What we definitely know is that Inv and ~Bad is safe k-inductive invariant. Since Inv is inductive, it follows that 115 | * after k steps, we stay in Inv, but we also know we cannot reach Bad, thus we reach Inv and ~Bad again. 116 | * 117 | * @param inductiveInvariant an inductive invariant of the system 118 | * @param k number of steps for which Bad in not reachable from the invariant 119 | * @param ts transition system 120 | * @return safe inductive invariant of the system 121 | */ 122 | PTRef IMC::computeFinalInductiveInvariant(PTRef inductiveInvariant, unsigned k, TransitionSystem const & ts) { 123 | SMTSolver solver(logic, SMTSolver::WitnessProduction::NONE); 124 | solver.assertProp(inductiveInvariant); 125 | solver.assertProp(ts.getQuery()); 126 | auto res = solver.check(); 127 | if (res == SMTSolver::Answer::UNSAT) { return inductiveInvariant; } 128 | // Otherwise compute safe inductive invariant from k-inductive invariant 129 | PTRef kinductive = logic.mkAnd(inductiveInvariant, logic.mkNot(ts.getQuery())); 130 | return kinductiveToInductive(kinductive, k, ts); 131 | } 132 | } // namespace golem 133 | -------------------------------------------------------------------------------- /src/engine/IMC.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, Konstantin Britikov 3 | * Copyright (c) 2023-2025, Martin Blicha 4 | * 5 | * SPDX-License-Identifier: MIT 6 | */ 7 | 8 | #ifndef GOLEM_IMC_H 9 | #define GOLEM_IMC_H 10 | 11 | #include "Options.h" 12 | #include "TransitionSystem.h" 13 | #include "TransitionSystemEngine.h" 14 | 15 | #include "osmt_solver.h" 16 | 17 | namespace golem { 18 | class IMC : public TransitionSystemEngine { 19 | Logic & logic; 20 | // Options const & options; 21 | int verbosity = 0; 22 | 23 | public: 24 | IMC(Logic & logic, Options const & options) : logic(logic) { 25 | verbosity = std::stoi(options.getOrDefault(Options::VERBOSE, "0")); 26 | computeWitness = options.getOrDefault(Options::COMPUTE_WITNESS, "") == "true"; 27 | } 28 | 29 | using TransitionSystemEngine::solve; 30 | 31 | private: 32 | TransitionSystemVerificationResult solve(TransitionSystem const & system) override; 33 | 34 | TransitionSystemVerificationResult finiteRun(TransitionSystem const & ts, unsigned k); 35 | 36 | PTRef computeFinalInductiveInvariant(PTRef inductiveInvariant, unsigned k, TransitionSystem const & ts); 37 | 38 | bool implies(PTRef antecedent, PTRef consequent) const; 39 | }; 40 | } // namespace golem 41 | 42 | #endif // GOLEM_IMC_H 43 | -------------------------------------------------------------------------------- /src/engine/Kind.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_KIND_H 8 | #define GOLEM_KIND_H 9 | 10 | # include "Options.h" 11 | #include "TransitionSystemEngine.h" 12 | #include "TransitionSystem.h" 13 | 14 | namespace golem { 15 | class Kind : public TransitionSystemEngine { 16 | Logic & logic; 17 | // Options const & options; 18 | int verbosity {0}; 19 | public: 20 | 21 | Kind(Logic & logic, Options const & options) : logic(logic) { 22 | verbosity = std::stoi(options.getOrDefault(Options::VERBOSE, "0")); 23 | computeWitness = options.getOrDefault(Options::COMPUTE_WITNESS, "") == "true"; 24 | } 25 | 26 | 27 | VerificationResult solve(ChcDirectedGraph const & graph) override; 28 | 29 | private: 30 | VerificationResult solveTransitionSystem(ChcDirectedGraph const & graph); 31 | TransitionSystemVerificationResult solveTransitionSystemInternal(TransitionSystem const & system); 32 | 33 | PTRef invariantFromForwardInduction(TransitionSystem const & transitionSystem, unsigned long k) const; 34 | PTRef invariantFromBackwardInduction(TransitionSystem const & transitionSystem, unsigned long k) const; 35 | 36 | }; 37 | } // namespace golem 38 | 39 | 40 | #endif //GOLEM_KIND_H 41 | -------------------------------------------------------------------------------- /src/engine/Lawi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_LAWI_H 8 | #define GOLEM_LAWI_H 9 | 10 | #include "Engine.h" 11 | #include "Options.h" 12 | 13 | namespace golem { 14 | /* 15 | * Implementation of Lazy Abstraction with Interpolants (also known as IMPACT algorithm) 16 | * 17 | * Implementation based on McMillan's original paper Lazy Abstraction with Interpolants, CAV 2006. 18 | * https://link.springer.com/chapter/10.1007/11817963_14 19 | * Another good paper: Algorithms for Software Model Checking: Predicate Abstraction vs. IMPACT, D. Beyer and P. Wendler, FMCAD 2012 20 | * https://ieeexplore.ieee.org/document/6462562 21 | */ 22 | class Lawi : public Engine { 23 | Logic & logic; 24 | Options const & options; 25 | 26 | public: 27 | Lawi(Logic & logic, Options const & options) : logic(logic), options(options) {} 28 | 29 | VerificationResult solve(ChcDirectedHyperGraph const & system) override; 30 | 31 | VerificationResult solve(ChcDirectedGraph const & system); 32 | }; 33 | } // namespace golem 34 | 35 | #endif // GOLEM_LAWI_H 36 | -------------------------------------------------------------------------------- /src/engine/PDKind.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023-2024, Stepan Henrych 3 | * Copyright (c) 2025, Martin Blicha 4 | 5 | * 6 | * SPDX-License-Identifier: MIT 7 | */ 8 | 9 | #ifndef GOLEM_PDKIND_H 10 | #define GOLEM_PDKIND_H 11 | 12 | #include "Options.h" 13 | #include "TransitionSystem.h" 14 | #include "TransitionSystemEngine.h" 15 | 16 | namespace golem { 17 | /** 18 | * Uses PDKind algorithm [1] to solve a transition system. 19 | * 20 | * [1] https://ieeexplore.ieee.org/document/7886665 21 | */ 22 | class PDKind : public TransitionSystemEngine { 23 | Logic & logic; 24 | 25 | public: 26 | PDKind(Logic & logic, Options const & options) : logic(logic) { 27 | if (options.hasOption(Options::COMPUTE_WITNESS)) { 28 | computeWitness = options.getOption(Options::COMPUTE_WITNESS) == "true"; 29 | } 30 | } 31 | 32 | private: 33 | [[nodiscard]] TransitionSystemVerificationResult solve(TransitionSystem const & system) override; 34 | }; 35 | } // namespace golem 36 | 37 | #endif // GOLEM_PDKIND_H -------------------------------------------------------------------------------- /src/engine/Spacer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_SPACER_H 8 | #define GOLEM_SPACER_H 9 | 10 | #include "Engine.h" 11 | #include "Options.h" 12 | 13 | namespace golem { 14 | class Spacer : public Engine { 15 | Logic & logic; 16 | Options const & options; 17 | 18 | public: 19 | Spacer(Logic & logic, Options const & options) : logic(logic), options(options) {} 20 | 21 | [[nodiscard]] VerificationResult solve(ChcDirectedHyperGraph const & system) override; 22 | }; 23 | } // namespace golem 24 | 25 | #endif // GOLEM_SPACER_H 26 | -------------------------------------------------------------------------------- /src/engine/SymbolicExecution.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_SYMBOLICEXECUTION_H 8 | #define GOLEM_SYMBOLICEXECUTION_H 9 | 10 | #include "Engine.h" 11 | #include "Options.h" 12 | 13 | namespace golem { 14 | class SymbolicExecution : public Engine { 15 | Options const & options; 16 | 17 | public: 18 | SymbolicExecution(Logic &, Options const & options) : options(options) {} 19 | 20 | VerificationResult solve(ChcDirectedHyperGraph const & graph) override; 21 | }; 22 | } // namespace golem 23 | 24 | #endif // GOLEM_SYMBOLICEXECUTION_H 25 | -------------------------------------------------------------------------------- /src/engine/TransitionSystemEngine.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "TransitionSystemEngine.h" 8 | 9 | #include "Common.h" 10 | #include "TransformationUtils.h" 11 | #include "TransitionSystem.h" 12 | #include "transformers/BasicTransformationPipelines.h" 13 | #include "transformers/SingleLoopTransformation.h" 14 | 15 | namespace golem { 16 | VerificationResult TransitionSystemEngine::solve(ChcDirectedHyperGraph const & graph) { 17 | auto pipeline = Transformations::towardsTransitionSystems(); 18 | auto transformationResult = pipeline.transform(std::make_unique(graph)); 19 | auto transformedGraph = std::move(transformationResult.first); 20 | auto translator = std::move(transformationResult.second); 21 | if (transformedGraph->isNormalGraph()) { 22 | auto normalGraph = transformedGraph->toNormalGraph(); 23 | auto res = solve(*normalGraph); 24 | return computeWitness ? translator->translate(std::move(res)) : std::move(res); 25 | } 26 | return VerificationResult(VerificationAnswer::UNKNOWN); 27 | } 28 | 29 | VerificationResult TransitionSystemEngine::solve(ChcDirectedGraph const & graph) { 30 | if (isTrivial(graph)) { 31 | return solveTrivial(graph); 32 | } 33 | if (isTransitionSystem(graph)) { 34 | auto ts = toTransitionSystem(graph); 35 | auto res = solve(*ts); 36 | return computeWitness ? translateTransitionSystemResult(res, graph, *ts) : VerificationResult(res.answer); 37 | } 38 | SingleLoopTransformation transformation; 39 | auto[ts, backtranslator] = transformation.transform(graph); 40 | assert(ts); 41 | auto res = solve(*ts); 42 | return computeWitness ? backtranslator->translate(res) : VerificationResult(res.answer); 43 | } 44 | 45 | TransitionSystemVerificationResult TransitionSystemEngine::solve(TransitionSystem const &) { 46 | return {VerificationAnswer::UNKNOWN, {0u}}; 47 | } 48 | } // namespace golem 49 | 50 | -------------------------------------------------------------------------------- /src/engine/TransitionSystemEngine.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef TRANSITIONSYSTEMENGINE_H 8 | #define TRANSITIONSYSTEMENGINE_H 9 | 10 | #include "Engine.h" 11 | 12 | namespace golem { 13 | class TransitionSystemEngine : public Engine { 14 | public: 15 | VerificationResult solve(ChcDirectedHyperGraph const &) override; 16 | virtual VerificationResult solve(ChcDirectedGraph const &); 17 | virtual TransitionSystemVerificationResult solve(TransitionSystem const &); 18 | 19 | protected: 20 | bool computeWitness{false}; 21 | }; 22 | } // namespace golem 23 | 24 | #endif //TRANSITIONSYSTEMENGINE_H 25 | -------------------------------------------------------------------------------- /src/graph/ChcGraphBuilder.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "ChcGraphBuilder.h" 8 | 9 | namespace golem { 10 | std::unique_ptr ChcGraphBuilder::buildGraph(NormalizedChcSystem const & system) { 11 | std::vector edges; 12 | ChcSystem const & chcSystem = *system.normalizedSystem; 13 | // Special case to cover initial clauses, we are adding artificial "TRUE" starting predicate 14 | SymRef init = logic.getSym_true(); 15 | for (auto const & clause : chcSystem.getClauses()) { 16 | auto const& head = clause.head; 17 | auto const& body = clause.body; 18 | auto headSymbol = logic.getSymRef(head.predicate.predicate); 19 | 20 | std::vector from; 21 | for (auto const& bodyPredicate : body.uninterpretedPart) { 22 | from.push_back(logic.getSymRef(bodyPredicate.predicate)); 23 | } 24 | if (from.empty()) { 25 | from.push_back(init); 26 | } 27 | edges.push_back(DirectedHyperEdge{.from = std::move(from), .to = headSymbol, .fla = body.interpretedPart, .id = {0}}); 28 | } 29 | return std::make_unique(std::move(edges), system.canonicalPredicateRepresentation, logic); 30 | } 31 | } // namespace golem -------------------------------------------------------------------------------- /src/graph/ChcGraphBuilder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_CHCGRAPHBUILDER_H 8 | #define GOLEM_CHCGRAPHBUILDER_H 9 | 10 | #include "ChcGraph.h" 11 | #include "Normalizer.h" 12 | 13 | namespace golem { 14 | class ChcGraphBuilder { 15 | Logic & logic; 16 | public: 17 | explicit ChcGraphBuilder(Logic & logic) : logic(logic) {} 18 | 19 | std::unique_ptr buildGraph(NormalizedChcSystem const & system); 20 | }; 21 | }// namespace golem 22 | 23 | 24 | #endif //GOLEM_CHCGRAPHBUILDER_H 25 | -------------------------------------------------------------------------------- /src/include/osmt_parser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_OSMT_PARSER_H 8 | #define GOLEM_OSMT_PARSER_H 9 | 10 | #ifdef OPENSMT_LOCAL_BUILD 11 | #include "parsers/smt2new/smt2newcontext.h" 12 | #else 13 | #include "opensmt/smt2newcontext.h" 14 | #endif // OPENSMT_LOCAL_BUILD 15 | 16 | #endif //GOLEM_OSMT_PARSER_H 17 | -------------------------------------------------------------------------------- /src/include/osmt_solver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_OSMT_LOGICS_H 8 | #define GOLEM_OSMT_LOGICS_H 9 | 10 | #ifdef OPENSMT_LOCAL_BUILD 11 | #include "api/MainSolver.h" 12 | #include "models/Model.h" 13 | #else 14 | #include "opensmt/MainSolver.h" 15 | #include "opensmt/Model.h" 16 | #endif // OPENSMT_LOCAL_BUILD 17 | 18 | using namespace opensmt; 19 | 20 | #endif //GOLEM_OSMT_LOGICS_H 21 | -------------------------------------------------------------------------------- /src/include/osmt_terms.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_OSMT_TERMS_H 8 | #define GOLEM_OSMT_TERMS_H 9 | 10 | #ifdef OPENSMT_LOCAL_BUILD 11 | #include "logics/ArithLogic.h" 12 | #include "pterms/PTRef.h" 13 | #include "symbols/SymRef.h" 14 | #include "common/TreeOps.h" 15 | #include "simplifiers//BoolRewriting.h" 16 | #include "rewriters/Substitutor.h" 17 | #include "rewriters/DivModRewriter.h" 18 | #include "rewriters/DistinctRewriter.h" 19 | #include "itehandler/IteHandler.h" 20 | #else 21 | #include "opensmt/ArithLogic.h" 22 | #include "opensmt/PTRef.h" 23 | #include "opensmt/SymRef.h" 24 | #include "opensmt/TreeOps.h" 25 | #include "opensmt/BoolRewriting.h" 26 | #include "opensmt/Substitutor.h" 27 | #include "opensmt/DivModRewriter.h" 28 | #include "opensmt/DistinctRewriter.h" 29 | #include "opensmt/IteHandler.h" 30 | #endif // OPENSMT_LOCAL_BUILD 31 | 32 | using namespace opensmt; 33 | 34 | #endif //GOLEM_OSMT_TERMS_H 35 | -------------------------------------------------------------------------------- /src/proofs/ProofSteps.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023, Matias Barandiaran 3 | * Copyright (c) 2024-2025, Martin Blicha 4 | * 5 | * SPDX-License-Identifier: MIT 6 | */ 7 | 8 | #ifndef GOLEM_PROOFSTEPS_H 9 | #define GOLEM_PROOFSTEPS_H 10 | 11 | #include "Term.h" 12 | #include "Witnesses.h" 13 | #include "graph/ChcGraph.h" 14 | #include "utils/SmtSolver.h" 15 | #include 16 | #include 17 | 18 | namespace golem { 19 | class Step { 20 | public: 21 | enum class StepType : char { ASSUME, STEP, ANCHOR }; 22 | struct Premises { 23 | Premises() = default; 24 | explicit Premises(std::size_t premise) : premises{premise} {} 25 | explicit Premises(std::vector premises) : premises{std::move(premises)} {} 26 | Premises(std::initializer_list premises) : premises{premises} {} 27 | std::vector premises; 28 | }; 29 | 30 | private: 31 | std::size_t stepId; 32 | StepType type; 33 | std::vector> clause; 34 | std::string rule; 35 | Premises premises; 36 | std::vector args; 37 | 38 | public: 39 | Step(std::size_t stepId, StepType type, std::vector> clause, std::string rule, 40 | Premises premises) 41 | : stepId(stepId), type(type), clause(std::move(clause)), rule(std::move(rule)), premises(std::move(premises)) {} 42 | Step(std::size_t stepId, StepType type, std::vector> clause, std::string rule, 43 | std::vector args) 44 | : stepId(stepId), type(type), clause(std::move(clause)), rule(std::move(rule)), args(std::move(args)) {} 45 | Step(std::size_t stepId, StepType type, std::vector> clause, std::string rule) 46 | : stepId(stepId), type(type), clause(std::move(clause)), rule(std::move(rule)) {} 47 | Step(std::size_t stepId, StepType type, std::vector> clause) 48 | : stepId(stepId), type(type), clause(std::move(clause)), rule(" ") {} 49 | 50 | [[nodiscard]] std::string printStepAlethe() const; 51 | [[nodiscard]] std::string printStepIntermediate() const; 52 | }; 53 | 54 | class Observer { 55 | public: 56 | virtual void update(Step const & step) = 0; 57 | virtual ~Observer() = default; 58 | }; 59 | 60 | class AlethePrintObserver : public Observer { 61 | std::ostream & out; 62 | 63 | public: 64 | explicit AlethePrintObserver(std::ostream & out) : out(out) {} 65 | void update(Step const & step) override { out << step.printStepAlethe(); } 66 | }; 67 | 68 | class [[maybe_unused]] CountingObserver : public Observer { 69 | std::size_t count = 0; 70 | 71 | public: 72 | CountingObserver() = default; 73 | ~CountingObserver() override { std::cout << count << std::endl; } 74 | void update(Step const &) override { ++count; } 75 | }; 76 | 77 | class IntermediatePrintObserver : public Observer { 78 | std::ostream & out; 79 | 80 | public: 81 | explicit IntermediatePrintObserver(std::ostream & out) : out(out) {} 82 | void update(Step const & step) override { out << step.printStepIntermediate(); } 83 | }; 84 | 85 | class StepHandler { 86 | 87 | InvalidityWitness::Derivation derivation; 88 | std::vector> originalAssertions; 89 | Normalizer::Equalities const & normalizingEqualities; 90 | ArithLogic & logic; 91 | ChcDirectedHyperGraph originalGraph; 92 | 93 | std::vector observers; 94 | 95 | std::size_t currentStep = 0; 96 | std::size_t trueRuleStep = 0; 97 | std::size_t nameIndex = 0; 98 | 99 | public: 100 | StepHandler(InvalidityWitness::Derivation derivation, std::vector> originalAssertions, 101 | Normalizer::Equalities const & normalizingEqualities, ArithLogic & logic, 102 | ChcDirectedHyperGraph originalGraph) 103 | : derivation(std::move(derivation)), 104 | originalAssertions(std::move(originalAssertions)), 105 | normalizingEqualities(normalizingEqualities), 106 | logic(logic), 107 | originalGraph(std::move(originalGraph)) {} 108 | 109 | void buildAletheProof(); 110 | void buildIntermediateProof(); 111 | 112 | void registerObserver(Observer * observer) { observers.push_back(observer); } 113 | 114 | void deRegisterObserver(Observer const * observer) { 115 | if (const auto it = std::find(observers.begin(), observers.end(), observer); it != observers.end()) { 116 | observers.erase(it); 117 | } 118 | } 119 | 120 | private: 121 | using InstantiationPairs = std::unordered_map; 122 | using TermPtr = std::shared_ptr; 123 | InstantiationPairs getInstPairs(std::size_t stepIndex, vec const & stepNormEq); 124 | 125 | /** Records steps to derive instantiated term and returns this term */ 126 | TermPtr instantiationSteps(std::size_t i, TermPtr const & quantifiedTerm); 127 | void buildAssumptionSteps(); 128 | std::size_t deriveLHSWithoutConstraint(std::shared_ptr const & simplifiedLHS, 129 | std::vector predicatePremises); 130 | 131 | std::size_t getOrCreateTrueStep(); 132 | 133 | void notifyObservers(Step const & step) const { 134 | for (Observer * observer : observers) { // notify all observers 135 | observer->update(step); 136 | } 137 | } 138 | 139 | std::size_t lastStep() const { return currentStep - 1; } 140 | void recordStep(std::vector> && clause, std::string rule, Step::Premises && premises); 141 | void recordForallInstStep(std::vector> && clause, 142 | std::vector && instantiationPairs); 143 | void recordAssumption(std::vector> && clause); 144 | 145 | /** Simplifies term by evaluating operations on constants, records the simplification steps */ 146 | using SimplifyResult = std::optional>; // Simplified term + congruence step id 147 | SimplifyResult simplify(TermPtr const & term, std::optional name = std::nullopt); 148 | SimplifyResult shortCircuitSimplifyITE(std::shared_ptr const & ite); 149 | // Assumes that arguments are already simplified 150 | TermPtr simplifyOpDirect(std::shared_ptr const & op); 151 | }; 152 | } // namespace golem 153 | 154 | #endif // GOLEM_PROOFSTEPS_H 155 | -------------------------------------------------------------------------------- /src/transformers/BasicTransformationPipelines.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "BasicTransformationPipelines.h" 8 | 9 | #include "ConstraintSimplifier.h" 10 | #include "EdgeInliner.h" 11 | #include "FalseClauseRemoval.h" 12 | #include "MultiEdgeMerger.h" 13 | #include "NodeEliminator.h" 14 | #include "RemoveUnreachableNodes.h" 15 | #include "SimpleChainSummarizer.h" 16 | #include "TrivialEdgePruner.h" 17 | 18 | namespace golem::Transformations { 19 | 20 | namespace { 21 | 22 | struct SlightlyBetterNodeEliminatorPredicate { 23 | bool operator()(SymRef, AdjacencyListsGraphRepresentation const &, ChcDirectedHyperGraph const & graph) const; 24 | }; 25 | 26 | bool SlightlyBetterNodeEliminatorPredicate::operator()( 27 | SymRef vertex, 28 | AdjacencyListsGraphRepresentation const & ar, 29 | ChcDirectedHyperGraph const & graph) const { 30 | if (not isNonLoopNode(vertex, ar, graph)) { return false; } 31 | // Here we do not consider hyperedges anymore 32 | if (hasHyperEdge(vertex, ar, graph)) { return false; } 33 | // We extend the definition of a simple node such that |incoming| x |outgoing| <= |incoming| + |outgoing| 34 | bool isSimple = [&]() { 35 | auto outgoingSize = ar.getOutgoingEdgesFor(vertex).size(); 36 | auto incomingSize = ar.getIncomingEdgesFor(vertex).size(); 37 | return incomingSize * outgoingSize <= incomingSize + outgoingSize; 38 | }(); 39 | return isSimple; 40 | } 41 | 42 | /** 43 | * This node eliminator is designed to eliminate some nodes that SimpleNodeEliminator preserves. 44 | * Typical example is head of an outer loop; in graph terminology, node s1 which has one incoming, one outgoing edge, 45 | * and then for some s2 there are edges s1 -> s2, s2 -> s2, s2 -> s1. 46 | * 47 | * For simplicity, we no longer consider hyperedges in this eliminator. 48 | */ 49 | class SlightlyBetterNodeEliminator : public NodeEliminator { 50 | public: 51 | SlightlyBetterNodeEliminator() : NodeEliminator(SlightlyBetterNodeEliminatorPredicate{}) {} 52 | }; 53 | } // namespace 54 | 55 | TransformationPipeline towardsTransitionSystems() { 56 | TransformationPipeline::pipeline_t stages; 57 | stages.push_back(std::make_unique()); 58 | stages.push_back(std::make_unique()); 59 | stages.push_back(std::make_unique()); 60 | stages.push_back(std::make_unique()); 61 | stages.push_back(std::make_unique()); 62 | TransformationPipeline pipeline(std::move(stages)); 63 | return pipeline; 64 | } 65 | 66 | TransformationPipeline defaultTransformationPipeline() { 67 | TransformationPipeline::pipeline_t stages; 68 | stages.push_back(std::make_unique()); 69 | stages.push_back(std::make_unique()); 70 | stages.push_back(std::make_unique()); 71 | stages.push_back(std::make_unique()); 72 | stages.push_back(std::make_unique()); 73 | stages.push_back(std::make_unique()); 74 | stages.push_back(std::make_unique()); 75 | stages.push_back(std::make_unique()); 76 | stages.push_back(std::make_unique()); 77 | stages.push_back(std::make_unique()); 78 | stages.push_back(std::make_unique()); 79 | stages.push_back(std::make_unique()); 80 | stages.push_back(std::make_unique()); 81 | stages.push_back(std::make_unique()); 82 | stages.push_back(std::make_unique()); 83 | return TransformationPipeline(std::move(stages)); 84 | } 85 | } // namespace golem::Transformations -------------------------------------------------------------------------------- /src/transformers/BasicTransformationPipelines.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_BASICTRANSFORMATIONPIPELINES_H 8 | #define GOLEM_BASICTRANSFORMATIONPIPELINES_H 9 | 10 | #include "TransformationPipeline.h" 11 | 12 | namespace golem::Transformations { 13 | 14 | TransformationPipeline towardsTransitionSystems(); 15 | 16 | TransformationPipeline defaultTransformationPipeline(); 17 | 18 | } // namespace golem::Transformations 19 | 20 | 21 | #endif //GOLEM_BASICTRANSFORMATIONPIPELINES_H 22 | -------------------------------------------------------------------------------- /src/transformers/CommonUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_COMMONUTILS_H 8 | #define GOLEM_COMMONUTILS_H 9 | 10 | #include "graph/ChcGraph.h" 11 | #include "Witnesses.h" 12 | 13 | namespace golem { 14 | class EdgeConverter { 15 | Logic & logic; 16 | TermUtils utils; 17 | TimeMachine timeMachine; 18 | VersionManager manager; 19 | NonlinearCanonicalPredicateRepresentation const & predicateRepresentation; 20 | public: 21 | EdgeConverter(Logic & logic, NonlinearCanonicalPredicateRepresentation const & predicateRepresentation) : 22 | logic(logic), utils(logic), timeMachine(logic), manager(logic), predicateRepresentation(predicateRepresentation) {} 23 | 24 | DirectedEdge operator()(DirectedHyperEdge const & edge) { 25 | assert(edge.from.size() == 1); 26 | auto source = edge.from[0]; 27 | auto target = edge.to; 28 | TermUtils::substitutions_map subst; 29 | { 30 | auto sourceVars = utils.predicateArgsInOrder(predicateRepresentation.getSourceTermFor(source)); 31 | for (PTRef sourceVar : sourceVars) { 32 | PTRef newVar = timeMachine.getVarVersionZero(manager.toBase(sourceVar)); 33 | subst.insert({sourceVar, newVar}); 34 | } 35 | } 36 | { 37 | auto targetVars = utils.predicateArgsInOrder(predicateRepresentation.getTargetTermFor(target)); 38 | for (PTRef targetVar : targetVars) { 39 | PTRef newVar = timeMachine.sendVarThroughTime(timeMachine.getVarVersionZero(manager.toBase(targetVar)), 1); 40 | subst.insert({targetVar, newVar}); 41 | } 42 | } 43 | 44 | PTRef newLabel = TermUtils(logic).varSubstitute(edge.fla.fla, subst); 45 | return DirectedEdge{.from = edge.from[0], .to = edge.to, .fla = {newLabel}, .id = {edge.id}}; 46 | } 47 | }; 48 | 49 | class VersionedPredicate { 50 | Logic & logic; 51 | TermUtils utils; 52 | TimeMachine timeMachine; 53 | VersionManager manager; 54 | NonlinearCanonicalPredicateRepresentation const & predicateRepresentation; 55 | public: 56 | VersionedPredicate(Logic & logic, NonlinearCanonicalPredicateRepresentation const & predicateRepresentation) : 57 | logic(logic), utils(logic), timeMachine(logic), manager(logic), predicateRepresentation(predicateRepresentation) {} 58 | 59 | PTRef operator()(SymRef predicateSymbol) { 60 | vec baseVars; 61 | auto sourceVars = utils.predicateArgsInOrder(predicateRepresentation.getSourceTermFor(predicateSymbol)); 62 | for (PTRef sourceVar : sourceVars) { 63 | PTRef newVar = timeMachine.getVarVersionZero(manager.toBase(sourceVar)); 64 | baseVars.push(newVar); 65 | } 66 | return logic.insertTerm(predicateSymbol, std::move(baseVars)); 67 | } 68 | }; 69 | 70 | InvalidityWitness::Derivation replaceSummarizingStep( 71 | InvalidityWitness::Derivation const & derivation, 72 | std::size_t stepIndex, 73 | std::vector const & replacedChain, 74 | DirectedHyperEdge const & replacingEdge, 75 | NonlinearCanonicalPredicateRepresentation const & predicateRepresentation, 76 | Logic & logic 77 | ); 78 | 79 | using ContractionInfo = std::pair>; 80 | 81 | InvalidityWitness::Derivation expandStepWithHyperEdge( 82 | InvalidityWitness::Derivation const & derivation, 83 | std::size_t stepIndex, 84 | ContractionInfo const & contractionInfo, 85 | NonlinearCanonicalPredicateRepresentation const & predicateRepresentation, 86 | Logic & logic 87 | ); 88 | 89 | struct EdgeTranslator { 90 | ChcDirectedGraph const & graph; 91 | LocationVarMap const & locationVarMap; 92 | PositionVarMap const & positionVarMap; 93 | 94 | mutable vec auxiliaryVariablesSeen; 95 | 96 | PTRef translateEdge(DirectedEdge const & edge) const; 97 | }; 98 | } // namespace golem 99 | 100 | #endif //GOLEM_COMMONUTILS_H 101 | -------------------------------------------------------------------------------- /src/transformers/ConstraintSimplifier.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "ConstraintSimplifier.h" 8 | 9 | namespace helper { 10 | class Normalizer { 11 | Logic & logic; 12 | public: 13 | Normalizer(Logic & logic) : logic(logic) {} 14 | 15 | PTRef eliminateItes(PTRef fla); 16 | PTRef eliminateDivMod(PTRef fla); 17 | PTRef eliminateDistincts(PTRef fla); 18 | }; 19 | 20 | PTRef Normalizer::eliminateDivMod(PTRef fla) { 21 | ArithLogic * lalogic = dynamic_cast(&logic); 22 | if (lalogic and lalogic->hasIntegers()) { 23 | return DivModRewriter(*lalogic).rewrite(fla); 24 | } 25 | return fla; 26 | } 27 | 28 | PTRef Normalizer::eliminateItes(PTRef fla) { 29 | return IteHandler(logic).rewrite(fla); 30 | } 31 | 32 | PTRef Normalizer::eliminateDistincts(PTRef fla) { 33 | return DistinctRewriter(logic).rewrite(fla); 34 | } 35 | 36 | } // namespace helper 37 | 38 | namespace golem { 39 | Transformer::TransformationResult ConstraintSimplifier::transform(std::unique_ptr graph) { 40 | 41 | Logic & logic = graph->getLogic(); 42 | TermUtils utils(logic); 43 | graph->forEachEdge([&](auto & edge) { 44 | PTRef constraint = edge.fla.fla; 45 | helper::Normalizer normalizer(logic); 46 | constraint = normalizer.eliminateItes(constraint); 47 | constraint = normalizer.eliminateDivMod(constraint); 48 | constraint = normalizer.eliminateDistincts(constraint); 49 | 50 | vec stateVars; 51 | for (auto sourcePredicate : graph->getSourceTerms(edge.id)) { 52 | for (PTRef var : utils.predicateArgsInOrder(sourcePredicate)) { 53 | assert(logic.isVar(var)); 54 | stateVars.push(var); 55 | } 56 | } 57 | 58 | PTRef targetPredicate = graph->getNextStateVersion(edge.to); 59 | for (PTRef var : utils.predicateArgsInOrder(targetPredicate)) { 60 | assert(logic.isVar(var)); 61 | stateVars.push(var); 62 | } 63 | constraint = TrivialQuantifierElimination(logic).tryEliminateVarsExcept(stateVars, constraint); 64 | // Elimination of DIV/MOD and ITE might have introduced new auxiliary variables, we need to version them 65 | TimeMachine timeMachine(logic); 66 | VersionManager versionManager(logic); 67 | auto isVarToNormalize = [&](PTRef var) { 68 | return logic.isVar(var) and not versionManager.isTagged(var) and not timeMachine.isVersioned(var); 69 | }; 70 | auto localVars = matchingSubTerms(logic, constraint, isVarToNormalize); 71 | if (localVars.size() > 0) { 72 | TermUtils::substitutions_map subst; 73 | for (PTRef localVar : localVars) { 74 | // FIXME: This is not ideal, we can get a clash if the same DIV/MOD/ITE term occurs in different clauses 75 | // But we should anyway find a better way to handle these variables. Ideally unify this again with normalizer. 76 | subst.insert({localVar, timeMachine.getVarVersionZero(localVar)}); 77 | } 78 | constraint = utils.varSubstitute(constraint, subst); 79 | } 80 | edge.fla.fla = constraint; 81 | }); 82 | return {std::move(graph), std::make_unique()}; 83 | } 84 | } // namespace golem -------------------------------------------------------------------------------- /src/transformers/ConstraintSimplifier.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_CONSTRAINTSIMPLIFIER_H 8 | #define GOLEM_CONSTRAINTSIMPLIFIER_H 9 | 10 | #include "Transformer.h" 11 | 12 | namespace golem { 13 | class ConstraintSimplifier : public Transformer { 14 | 15 | public: 16 | TransformationResult transform(std::unique_ptr graph) override; 17 | 18 | class BackTranslator : public WitnessBackTranslator { 19 | public: 20 | InvalidityWitness translate(InvalidityWitness witness) override { return witness; } 21 | ValidityWitness translate(ValidityWitness witness) override { return witness; } 22 | }; 23 | }; 24 | } // namespace golem 25 | 26 | 27 | #endif //GOLEM_CONSTRAINTSIMPLIFIER_H 28 | -------------------------------------------------------------------------------- /src/transformers/EdgeInliner.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef EDGEINLINER_H 8 | #define EDGEINLINER_H 9 | 10 | #include "Transformer.h" 11 | 12 | namespace golem { 13 | class EdgeInliner : public Transformer { 14 | 15 | public: 16 | TransformationResult transform(std::unique_ptr graph) override; 17 | 18 | class BackTranslator : public WitnessBackTranslator { 19 | struct Deletion { 20 | DirectedHyperEdge deletedEdge; 21 | }; 22 | struct Replacement { 23 | DirectedHyperEdge addedEdge; 24 | DirectedHyperEdge removedEdge; 25 | DirectedHyperEdge predecessor; 26 | }; 27 | using Entry = std::variant; 28 | using Predecessors = std::vector; 29 | 30 | Logic & logic; 31 | NonlinearCanonicalPredicateRepresentation predicateRepresentation; 32 | std::vector entries; 33 | std::map predecessors; // at the time of removal 34 | public: 35 | BackTranslator(Logic & logic, NonlinearCanonicalPredicateRepresentation predicateRepresentation) 36 | : logic(logic), predicateRepresentation(std::move(predicateRepresentation)) {} 37 | 38 | InvalidityWitness translate(InvalidityWitness witness) override; 39 | ValidityWitness translate(ValidityWitness witness) override; 40 | 41 | void notifyEdgeDeleted(DirectedHyperEdge const &, Predecessors); 42 | 43 | void notifyEdgeReplaced(DirectedHyperEdge const & newEdge, DirectedHyperEdge const & removedEdge, 44 | DirectedHyperEdge const & predecessor, Predecessors); 45 | 46 | private: 47 | std::vector getAuxiliaryVarsFor(SymRef node) const; 48 | 49 | PTRef computeInterpolantFor(SymRef node, PTRef incoming, PTRef outgoing) const; 50 | }; 51 | }; 52 | } // namespace golem 53 | 54 | #endif // EDGEINLINER_H 55 | -------------------------------------------------------------------------------- /src/transformers/FalseClauseRemoval.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "FalseClauseRemoval.h" 8 | 9 | namespace golem { 10 | Transformer::TransformationResult FalseClauseRemoval::transform(std::unique_ptr graph) { 11 | graph->deleteFalseEdges(); 12 | return {std::move(graph), std::make_unique()}; 13 | } 14 | } // namespace golem -------------------------------------------------------------------------------- /src/transformers/FalseClauseRemoval.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_FALSECLAUSEREMOVAL_H 8 | #define GOLEM_FALSECLAUSEREMOVAL_H 9 | 10 | #include "Transformer.h" 11 | 12 | namespace golem { 13 | class FalseClauseRemoval : public Transformer { 14 | public: 15 | TransformationResult transform(std::unique_ptr graph) override; 16 | 17 | class BackTranslator : public WitnessBackTranslator { 18 | public: 19 | InvalidityWitness translate(InvalidityWitness witness) override { return witness; } 20 | ValidityWitness translate(ValidityWitness witness) override { return witness; } 21 | }; 22 | }; 23 | } // namespace golem 24 | 25 | #endif //GOLEM_FALSECLAUSEREMOVAL_H 26 | -------------------------------------------------------------------------------- /src/transformers/MultiEdgeMerger.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "MultiEdgeMerger.h" 8 | 9 | #include "utils/SmtSolver.h" 10 | 11 | namespace golem { 12 | Transformer::TransformationResult MultiEdgeMerger::transform(std::unique_ptr graph) { 13 | auto backtranslator = std::make_unique(graph->getLogic(), graph->predicateRepresentation()); 14 | backtranslator->mergedEdges = graph->mergeMultiEdges(); 15 | return {std::move(graph), std::move(backtranslator)}; 16 | } 17 | 18 | InvalidityWitness MultiEdgeMerger::BackTranslator::translate(InvalidityWitness witness) { 19 | if (mergedEdges.empty()) { return witness; } 20 | auto & derivation = witness.getDerivation(); 21 | for (auto & step : derivation) { 22 | auto eid = step.clauseId; 23 | auto it = std::find_if(mergedEdges.begin(), mergedEdges.end(), 24 | [eid](auto const & mergedEntry) { return mergedEntry.second.id == eid; }); 25 | if (it == mergedEdges.end()) { continue; } 26 | auto const & replacedEdges = it->first; 27 | auto const & replacingEdge = it->second; 28 | // Figure out which of the replaced edges can be used to derive the same fact 29 | TermUtils utils(logic); 30 | TermUtils::substitutions_map subst; 31 | { // build the substitution map 32 | auto fillVariables = [&](PTRef derivedFact, PTRef normalizedPredicate) { 33 | assert(logic.getSymRef(derivedFact) == logic.getSymRef(normalizedPredicate)); 34 | auto const & term = logic.getPterm(derivedFact); 35 | (void)term; 36 | assert(std::all_of(term.begin(), term.end(), [&](PTRef arg) { return logic.isConstant(arg); })); 37 | utils.mapFromPredicate(normalizedPredicate, derivedFact, subst); 38 | }; 39 | // collect values from the derived fact 40 | auto targetVertex = replacingEdge.to; 41 | assert(predicateRepresentation.hasRepresentationFor(targetVertex)); 42 | PTRef targetTerm = predicateRepresentation.getTargetTermFor(targetVertex); 43 | PTRef derivedFact = step.derivedFact; 44 | assert(logic.getPterm(targetTerm).size() == logic.getPterm(derivedFact).size()); 45 | assert(logic.getSymRef(targetTerm) == targetVertex and logic.getSymRef(derivedFact) == targetVertex); 46 | fillVariables(derivedFact, targetTerm); 47 | // and collect values from the premise as well 48 | auto const & premises = step.premises; 49 | assert(premises.size() == 1); // TODO: Generalize this when we generalize MultiEdgeMerge to HyperEdges 50 | auto const & premiseStep = derivation[premises[0]]; 51 | auto sourceVertex = replacingEdge.from[0]; 52 | PTRef premise = premiseStep.derivedFact; 53 | PTRef sourceTerm = predicateRepresentation.getSourceTermFor(sourceVertex); 54 | assert(logic.getPterm(sourceTerm).size() == logic.getPterm(premise).size()); 55 | assert(logic.getSymRef(sourceTerm) == sourceVertex and logic.getSymRef(premise) == sourceVertex); 56 | fillVariables(premise, sourceTerm); 57 | } // substitution map is built 58 | 59 | auto chosenEdgeIndex = [&]() -> std::optional { 60 | std::vector evaluatedLabels; 61 | for (std::size_t i = 0u; i < replacedEdges.size(); ++i) { 62 | PTRef evaluatedLabel = utils.varSubstitute(replacedEdges[i].fla.fla, subst); 63 | if (evaluatedLabel == logic.getTerm_true()) { return i; } 64 | evaluatedLabels.push_back(evaluatedLabel); 65 | } 66 | // No edge evaluate to true, try to find one with satisfiable label 67 | for (std::size_t i = 0u; i < evaluatedLabels.size(); ++i) { 68 | if (evaluatedLabels[i] == logic.getTerm_false()) { continue; } 69 | SMTSolver solver(logic, SMTSolver::WitnessProduction::NONE); 70 | solver.assertProp(evaluatedLabels[i]); 71 | if (solver.check() == SMTSolver::Answer::SAT) { return i; } 72 | } 73 | return {}; 74 | }(); 75 | if (not chosenEdgeIndex.has_value()) { 76 | return InvalidityWitness{}; // TODO: Change API to allow return NoWitness with reason 77 | } 78 | auto const & chosenEdge = replacedEdges[chosenEdgeIndex.value()]; 79 | step.clauseId = chosenEdge.id; 80 | } 81 | return witness; 82 | } 83 | 84 | ValidityWitness MultiEdgeMerger::BackTranslator::translate(ValidityWitness witness) { 85 | return witness; 86 | } 87 | } // namespace golem -------------------------------------------------------------------------------- /src/transformers/MultiEdgeMerger.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2023, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_MULTIEDGEMERGER_H 8 | #define GOLEM_MULTIEDGEMERGER_H 9 | 10 | #include "Transformer.h" 11 | 12 | namespace golem { 13 | class MultiEdgeMerger : public Transformer { 14 | public: 15 | TransformationResult transform(std::unique_ptr graph) override; 16 | 17 | class BackTranslator : public WitnessBackTranslator { 18 | public: 19 | BackTranslator(Logic & logic, NonlinearCanonicalPredicateRepresentation predicateRepresentation) 20 | : logic(logic), predicateRepresentation(std::move(predicateRepresentation)) {} 21 | 22 | InvalidityWitness translate(InvalidityWitness witness) override; 23 | 24 | ValidityWitness translate(ValidityWitness witness) override; 25 | 26 | using MergedEdges = ChcDirectedHyperGraph::MergedEdges; 27 | 28 | MergedEdges mergedEdges{}; 29 | Logic & logic; 30 | NonlinearCanonicalPredicateRepresentation predicateRepresentation; 31 | }; 32 | }; 33 | } // namespace golem 34 | 35 | #endif // GOLEM_MULTIEDGEMERGER_H 36 | -------------------------------------------------------------------------------- /src/transformers/NestedLoopTransformation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024, Konstantin Britikov 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_NESTED_LOOP_TRANSFORMATION_H 8 | #define GOLEM_NESTED_LOOP_TRANSFORMATION_H 9 | 10 | #include "CommonUtils.h" 11 | #include "TransitionSystem.h" 12 | #include "Witnesses.h" 13 | #include "graph/ChcGraph.h" 14 | 15 | namespace golem { 16 | /** 17 | * This class implements a transformation from a general linear CHC system to a linear CHC system without 18 | * nested loops. 19 | * The implementation is influenced by the algorithm used in SingleLoopTransformation.cc 20 | * 21 | * The idea behind the transformation is the following. 22 | * First we parse CHC DAG, detecting loops. 23 | * We introduce a separate boolean variable for each location inside of the loop (predicate, vertex in the CHC graph). 24 | * For each predicate and each argument of the predicate we add a new state variable of the transition system. 25 | * Then, for each edge (clause), we create a new disjunct of the transition relation. 26 | * This fragment captures the changes to the variables as defined by the edge constraint. 27 | * The fragment enforces that the current source location is variable is true and next target variable is true. 28 | * All other location variables are false (or alternatively keep their previous value). 29 | * The edge's constraint is translated to be over corresponding state variables (state vars from source, next state 30 | * vars from target), all other variables are unchanged. 31 | * 32 | * Backtranslating witness from transition system is currently based on the following reasoning: 33 | * To backtranslate proof, we have to identify which disjunct of the transition relation was used to make each step 34 | * in the error trace of the transition system. From that we can deduce which edge needs to be taken 35 | * (and with what values) in the CHC graph. 36 | * 37 | * For the model, we compute restriction of the TS invariant to each location by substituting appropriate constant 38 | * values for location variables. This may still leave some undesired variables in the invariant. We currently make 39 | * best effort to eliminate these variables by simplifying the formula. 40 | */ 41 | class NestedLoopTransformation { 42 | 43 | public: 44 | class WitnessBackTranslator { 45 | ChcDirectedGraph const & initialGraph; 46 | ChcDirectedGraph const & graph; 47 | std::vector loopContractionInfos; 48 | 49 | public: 50 | WitnessBackTranslator(ChcDirectedGraph const & initialGraph, ChcDirectedGraph const & graph, 51 | std::vector _loops) 52 | : initialGraph(initialGraph), graph(graph), loopContractionInfos(_loops) {} 53 | 54 | VerificationResult translate(VerificationResult & result); 55 | 56 | private: 57 | template using ErrorOr = std::variant; 58 | 59 | ErrorOr translateErrorPath(InvalidityWitness); 60 | 61 | ErrorOr translateInvariant(ValidityWitness); 62 | 63 | std::unordered_set getVarsForVertex(WitnessInfo) const; 64 | }; 65 | 66 | std::tuple, std::unique_ptr> 67 | transform(ChcDirectedGraph const & graph); 68 | }; 69 | } // namespace golem 70 | 71 | #endif // GOLEM_NESTED_LOOP_TRANSFORMATION_H 72 | -------------------------------------------------------------------------------- /src/transformers/NodeEliminator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_NODEELIMINATOR_H 8 | #define GOLEM_NODEELIMINATOR_H 9 | 10 | #include "Transformer.h" 11 | 12 | namespace golem { 13 | /* 14 | * Transformation pass that eliminates some nodes from the graph, using contraction. 15 | * 16 | * The predicate determining the nodes to eliminate is passed to the constructor. 17 | */ 18 | class NodeEliminator : public Transformer { 19 | using predicate_t = std::function; 20 | public: 21 | explicit NodeEliminator(predicate_t shouldEliminateNode) : shouldEliminateNode(std::move(shouldEliminateNode)) {} 22 | 23 | TransformationResult transform(std::unique_ptr graph) override; 24 | 25 | class BackTranslator : public WitnessBackTranslator { 26 | public: 27 | using ContractionResult = ChcDirectedHyperGraph::VertexContractionResult; 28 | 29 | BackTranslator(Logic & logic, NonlinearCanonicalPredicateRepresentation predicateRepresentation) 30 | : logic(logic), predicateRepresentation(std::move(predicateRepresentation)) {} 31 | 32 | InvalidityWitness translate(InvalidityWitness witness) override; 33 | 34 | ValidityWitness translate(ValidityWitness witness) override; 35 | 36 | void notifyRemovedVertex(SymRef sym, ContractionResult && edges); 37 | private: 38 | std::unordered_map nodeInfo; 39 | std::vector removedNodes; 40 | Logic & logic; 41 | NonlinearCanonicalPredicateRepresentation predicateRepresentation; 42 | }; 43 | 44 | predicate_t shouldEliminateNode; 45 | }; 46 | 47 | struct SimpleNodeEliminatorPredicate { 48 | bool operator()(SymRef, AdjacencyListsGraphRepresentation const &, ChcDirectedHyperGraph const & graph) const; 49 | }; 50 | 51 | class SimpleNodeEliminator : public NodeEliminator { 52 | public: 53 | SimpleNodeEliminator() : NodeEliminator(SimpleNodeEliminatorPredicate()) {} 54 | }; 55 | } // namespace golem 56 | 57 | #endif //GOLEM_NODEELIMINATOR_H 58 | -------------------------------------------------------------------------------- /src/transformers/RemoveUnreachableNodes.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "RemoveUnreachableNodes.h" 8 | 9 | namespace golem { 10 | namespace { 11 | std::vector computeBackwardUnreachable(ChcDirectedHyperGraph const & graph, 12 | AdjacencyListsGraphRepresentation const & adjacencyLists) { 13 | std::unordered_set backwardReachable; 14 | std::vector queue; 15 | queue.push_back(graph.getExit()); 16 | backwardReachable.insert(graph.getExit()); 17 | backwardReachable.insert(graph.getEntry()); 18 | while (not queue.empty()) { 19 | auto vertex = queue.back(); 20 | queue.pop_back(); 21 | for (auto incomingId : adjacencyLists.getIncomingEdgesFor(vertex)) { 22 | auto const & sources = graph.getSources(incomingId); 23 | for (auto source : sources) { 24 | auto inserted = backwardReachable.insert(source); 25 | if (inserted.second) { queue.push_back(source); } 26 | } 27 | } 28 | } 29 | 30 | std::vector backwardUnreachable; 31 | for (auto node : adjacencyLists.getNodes()) { 32 | if (backwardReachable.find(node) == backwardReachable.end()) { backwardUnreachable.push_back(node); } 33 | } 34 | return backwardUnreachable; 35 | } 36 | 37 | std::vector computeForwardUnreachable(ChcDirectedHyperGraph const & graph, 38 | AdjacencyListsGraphRepresentation const & adjacencyLists) { 39 | std::vector queue; 40 | std::unordered_set forwardReachable; 41 | forwardReachable.insert(graph.getEntry()); 42 | queue.push_back(graph.getEntry()); 43 | while (not queue.empty()) { 44 | auto node = queue.back(); 45 | queue.pop_back(); 46 | for (EId eid : adjacencyLists.getOutgoingEdgesFor(node)) { 47 | auto target = graph.getTarget(eid); 48 | if (forwardReachable.count(target)) { continue; } 49 | auto const & sources = graph.getSources(eid); 50 | bool allSourcesReachable = std::all_of(sources.begin(), sources.end(), 51 | [&](auto source) { return forwardReachable.count(source); }); 52 | if (allSourcesReachable) { 53 | forwardReachable.insert(target); 54 | queue.push_back(target); 55 | } 56 | } 57 | } 58 | 59 | std::vector forwardUnreachable; 60 | for (auto node : adjacencyLists.getNodes()) { 61 | if (forwardReachable.find(node) == forwardReachable.end()) { forwardUnreachable.push_back(node); } 62 | } 63 | return forwardUnreachable; 64 | } 65 | 66 | } // namespace 67 | 68 | Transformer::TransformationResult RemoveUnreachableNodes::transform(std::unique_ptr graph) { 69 | auto adjacencyLists = AdjacencyListsGraphRepresentation::from(*graph); 70 | auto & logic = graph->getLogic(); 71 | 72 | if (adjacencyLists.getIncomingEdgesFor(graph->getExit()).empty() or 73 | adjacencyLists.getOutgoingEdgesFor(graph->getEntry()).empty()) { 74 | // All edges can be removed 75 | // We return empty graph, and remember all removed vertices for backtranslation 76 | auto allNodes = adjacencyLists.getNodes(); 77 | allNodes.erase( 78 | std::remove_if(allNodes.begin(), allNodes.end(), 79 | [&](SymRef node) { return node == graph->getEntry() or node == graph->getExit(); }), 80 | allNodes.end()); 81 | auto backtranslator = 82 | adjacencyLists.getIncomingEdgesFor(graph->getExit()).empty() 83 | ? std::make_unique(graph->getLogic(), std::vector{}, std::move(allNodes)) 84 | : std::make_unique(graph->getLogic(), std::move(allNodes), std::vector{}); 85 | return {ChcDirectedHyperGraph::makeEmpty(logic), std::move(backtranslator)}; 86 | } 87 | 88 | auto backwardUnreachable = computeBackwardUnreachable(*graph, adjacencyLists); 89 | for (auto node : backwardUnreachable) { 90 | graph->deleteNode(node); 91 | } 92 | if (not backwardUnreachable.empty()) { adjacencyLists = AdjacencyListsGraphRepresentation::from(*graph); } 93 | auto forwardUnreachable = computeForwardUnreachable(*graph, adjacencyLists); 94 | for (auto node : forwardUnreachable) { 95 | graph->deleteNode(node); 96 | } 97 | 98 | return {std::move(graph), 99 | std::make_unique(logic, std::move(forwardUnreachable), std::move(backwardUnreachable))}; 100 | } 101 | 102 | ValidityWitness RemoveUnreachableNodes::BackTranslator::translate(ValidityWitness witness) { 103 | if (unreachableFromTrue.empty() and backwardUnreachableFromFalse.empty()) { return witness; } 104 | auto definitions = witness.getDefinitions(); 105 | for (auto node : backwardUnreachableFromFalse) { 106 | assert(definitions.find(node) == definitions.end()); 107 | definitions.insert({node, logic.getTerm_true()}); 108 | } 109 | for (auto node : unreachableFromTrue) { 110 | assert(definitions.find(node) == definitions.end()); 111 | definitions.insert({node, logic.getTerm_false()}); 112 | } 113 | return ValidityWitness(std::move(definitions)); 114 | } 115 | } // namespace golem -------------------------------------------------------------------------------- /src/transformers/RemoveUnreachableNodes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_REMOVEUNREACHABLENODES_H 8 | #define GOLEM_REMOVEUNREACHABLENODES_H 9 | 10 | #include "Transformer.h" 11 | 12 | namespace golem { 13 | class RemoveUnreachableNodes : public Transformer { 14 | public: 15 | TransformationResult transform(std::unique_ptr graph) override; 16 | 17 | class BackTranslator : public WitnessBackTranslator { 18 | Logic & logic; 19 | std::vector unreachableFromTrue; 20 | std::vector backwardUnreachableFromFalse; 21 | 22 | public: 23 | BackTranslator(Logic & logic, std::vector && unreachableFromTrue, 24 | std::vector && backwardUnreachableFromFalse) 25 | : logic(logic), 26 | unreachableFromTrue(std::move(unreachableFromTrue)), 27 | backwardUnreachableFromFalse(std::move(backwardUnreachableFromFalse)) {} 28 | 29 | InvalidityWitness translate(InvalidityWitness witness) override { return witness; } 30 | ValidityWitness translate(ValidityWitness witness) override; 31 | }; 32 | }; 33 | } // namespace golem 34 | 35 | #endif // GOLEM_REMOVEUNREACHABLENODES_H 36 | -------------------------------------------------------------------------------- /src/transformers/SimpleChainSummarizer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "SimpleChainSummarizer.h" 8 | 9 | #include "CommonUtils.h" 10 | 11 | #include "utils/SmtSolver.h" 12 | 13 | namespace golem { 14 | Transformer::TransformationResult SimpleChainSummarizer::transform(std::unique_ptr graph) { 15 | auto translator = std::make_unique(graph->getLogic(), graph->predicateRepresentation()); 16 | while(true) { 17 | AdjacencyListsGraphRepresentation adjacencyList = AdjacencyListsGraphRepresentation::from(*graph); 18 | auto isTrivial = [&](SymRef sym) { 19 | auto const & incoming = adjacencyList.getIncomingEdgesFor(sym); 20 | if (incoming.size() != 1) { return false; } 21 | auto const & outgoing = adjacencyList.getOutgoingEdgesFor(sym); 22 | if (outgoing.size() != 1) { return false; } 23 | EId const in = incoming[0]; 24 | EId const out = outgoing[0]; 25 | return in != out and graph->getSources(in).size() == 1 and graph->getSources(out).size() == 1 and graph->getSources(in)[0] != graph->getTarget(out); 26 | }; 27 | auto vertices = graph->getVertices(); 28 | auto it = std::find_if(vertices.begin(), vertices.end(), isTrivial); 29 | if (it == vertices.end()) { break; } 30 | auto trivialVertex = *it; 31 | auto trivialChain = [&](SymRef vertex) { 32 | std::vector edges; 33 | auto current = vertex; 34 | do { 35 | auto const & outgoing = adjacencyList.getOutgoingEdgesFor(current); 36 | assert(outgoing.size() == 1); 37 | edges.push_back(outgoing[0]); 38 | current = graph->getTarget(outgoing[0]); 39 | } while (isTrivial(current) and current != vertex); 40 | auto last = current; 41 | current = vertex; 42 | while (isTrivial(current)) { 43 | auto const & incoming = adjacencyList.getIncomingEdgesFor(current); 44 | assert(incoming.size() == 1); 45 | auto const & sources = graph->getSources(incoming[0]); 46 | assert(sources.size() == 1); 47 | current = sources[0]; 48 | if (current == last) { break; } // This means we have come full circle; we do not add the last edge. 49 | edges.insert(edges.begin(), incoming[0]); 50 | } 51 | return edges; 52 | }(trivialVertex); 53 | std::vector summarizedChain; 54 | std::transform(trivialChain.begin(), trivialChain.end(), std::back_inserter(summarizedChain), [&](EId eid) { 55 | // std::cout << "Edge in chain: " << logic.pp(graph->getEdgeLabel(eid)) << std::endl; 56 | return graph->getEdge(eid); 57 | }); 58 | auto summaryEdge = graph->contractTrivialChain(trivialChain); 59 | // std::cout << "Summary edge: " << logic.pp(summaryEdge.fla.fla) << std::endl; 60 | translator->addSummarizedChain({summarizedChain, summaryEdge}); 61 | } 62 | return {std::move(graph), std::move(translator)}; 63 | } 64 | 65 | InvalidityWitness SimpleChainSummarizer::BackTranslator::translate(InvalidityWitness witness) { 66 | if (summarizedChains.empty()) { return witness; } 67 | 68 | for (auto const & [chain, replacingEdge] : summarizedChains) { 69 | EId eid = replacingEdge.id; 70 | // replace all occurrences of this edge 71 | while(true) { 72 | auto const & derivation = witness.getDerivation(); 73 | auto it = std::find_if(derivation.begin(), derivation.end(), [eid](auto const & step){ return step.clauseId == eid; }); 74 | if (it == derivation.end()) { break; } 75 | std::size_t stepIndex = it - derivation.begin(); 76 | auto newDerivation = replaceSummarizingStep(derivation, stepIndex, chain, replacingEdge, predicateRepresentation, logic); 77 | witness.setDerivation(std::move(newDerivation)); 78 | } 79 | } 80 | return witness; 81 | } 82 | 83 | ValidityWitness SimpleChainSummarizer::BackTranslator::translate(ValidityWitness witness) { 84 | if (summarizedChains.empty()) { return witness; } 85 | auto definitions = witness.getDefinitions(); 86 | std::reverse(summarizedChains.begin(), summarizedChains.end()); 87 | TermUtils utils(logic); 88 | VersionManager manager(logic); 89 | for (auto && [chain, summary] : summarizedChains) { 90 | // Compute definitions for vertices on the chain using path interpolants 91 | SMTSolver solver(logic, SMTSolver::WitnessProduction::ONLY_INTERPOLANTS); 92 | assert(summary.from.size() == 1); 93 | assert(definitions.find(summary.from.front()) != definitions.end()); 94 | PTRef sourceInterpretation = manager.baseFormulaToSource(definitions.at(summary.from.front())); 95 | solver.assertProp(sourceInterpretation); 96 | for (auto const & edge : chain) { 97 | TermUtils::substitutions_map substitutionsMap; 98 | auto target = edge.to; 99 | utils.mapFromPredicate(predicateRepresentation.getTargetTermFor(target), predicateRepresentation.getSourceTermFor(target), substitutionsMap); 100 | PTRef updatedLabel = utils.varSubstitute(edge.fla.fla, substitutionsMap); 101 | solver.assertProp(updatedLabel); 102 | } 103 | assert(definitions.find(summary.to) != definitions.end()); 104 | PTRef targetInterpretation = manager.baseFormulaToSource(definitions.at(summary.to)); 105 | solver.assertProp(logic.mkNot(targetInterpretation)); 106 | auto res = solver.check(); 107 | if (res != SMTSolver::Answer::UNSAT) { 108 | //throw std::logic_error("SimpleChainBackTranslator could not recompute solution!"); 109 | std::cerr << "; SimpleChainBackTranslator could not recompute solution! Solver could not prove UNSAT!" << std::endl; 110 | return ValidityWitness(); 111 | } 112 | auto itpCtx = solver.getInterpolationContext(); 113 | std::vector partitionings; 114 | ipartitions_t p = 1; 115 | for (auto i = 0u; i < chain.size() - 1; ++i) { 116 | opensmt::setbit(p, i + 1); // MB: +1 for the source interpretation 117 | partitionings.push_back(p); 118 | } 119 | vec itps; 120 | itpCtx->getPathInterpolants(itps, partitionings); 121 | for (auto i = 0u; i < chain.size() - 1; ++i) { 122 | auto target = chain[i].to; 123 | if (definitions.count(target) > 0) { 124 | std::cerr << "; Unexpected situation in SimpleChainBackTranslator: Predicate already has a solution!" << std::endl; 125 | return ValidityWitness(); 126 | } 127 | definitions.insert({target, VersionManager(logic).sourceFormulaToBase(itps[i])}); 128 | } 129 | } 130 | return ValidityWitness(std::move(definitions)); 131 | } 132 | } // namespace golem -------------------------------------------------------------------------------- /src/transformers/SimpleChainSummarizer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_SIMPLECHAINSUMMARIZER_H 8 | #define GOLEM_SIMPLECHAINSUMMARIZER_H 9 | 10 | #include "Transformer.h" 11 | 12 | namespace golem { 13 | class SimpleChainSummarizer : public Transformer { 14 | public: 15 | TransformationResult transform(std::unique_ptr graph) override; 16 | 17 | SimpleChainSummarizer() = default; 18 | 19 | class BackTranslator : public WitnessBackTranslator { 20 | public: 21 | BackTranslator(Logic & logic, NonlinearCanonicalPredicateRepresentation predicateRepresentation) 22 | : logic(logic), predicateRepresentation(std::move(predicateRepresentation)) {} 23 | 24 | InvalidityWitness translate(InvalidityWitness witness) override; 25 | 26 | ValidityWitness translate(ValidityWitness witness) override; 27 | 28 | using SummarizedChain = std::pair, DirectedHyperEdge>; 29 | 30 | void addSummarizedChain(SummarizedChain && chain) { summarizedChains.push_back(std::move(chain)); } 31 | 32 | private: 33 | std::vector summarizedChains; 34 | Logic & logic; 35 | NonlinearCanonicalPredicateRepresentation predicateRepresentation; 36 | }; 37 | }; 38 | } // namespace golem 39 | 40 | #endif //GOLEM_SIMPLECHAINSUMMARIZER_H 41 | -------------------------------------------------------------------------------- /src/transformers/SingleLoopTransformation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | #ifndef GOLEM_SINGLELOOPTRANSFORMATION_H 7 | #define GOLEM_SINGLELOOPTRANSFORMATION_H 8 | 9 | #include "CommonUtils.h" 10 | #include "TransitionSystem.h" 11 | #include "Witnesses.h" 12 | #include "graph/ChcGraph.h" 13 | 14 | namespace golem { 15 | /** 16 | * This class implements a transformation from a general linear CHC system to a transition system (defined by initial 17 | * states, transition relation and bad states). 18 | * The implementation follows the algorithm described in the short paper 19 | * Horn2VMT: Translating Horn Reachability into Transition Systems 20 | * presented in HCVS 2020. See, e.g., https://www.osti.gov/biblio/1783647 21 | * 22 | * The idea behind the transformation is the following. 23 | * We introduce a separate boolean variable for each location (predicate, vertex in the CHC graph). 24 | * For each predicate and each argument of the predicate we add a new state variable of the transition system. 25 | * Then, for each edge (clause), we create a new disjunct of the transition relation. 26 | * This fragment captures the changes to the variables as defined by the edge constraint. 27 | * The fragment enforces that the current source location is variable is true and next target variable is true. 28 | * All other location variables are false (or alternatively keep their previous value). 29 | * The edge's constraint is translated to be over corresponding state variables (state vars from source, next state 30 | * vars from target), all other variables are unchanged. 31 | * 32 | * Backtranslating witness from transition system is currently based on the following reasoning: 33 | * To backtranslate proof, we have to identify which disjunct of the transition relation was used to make each step 34 | * in the error trace of the transition system. From that we can deduce which edge needs to be taken 35 | * (and with what values) in the CHC graph. 36 | * 37 | * For the model, we compute restriction of the TS invariant to each location by substituting appropriate constant 38 | * values for location variables. This may still leave some undesired variables in the invariant. We currently make 39 | * best effort to eliminate these variables by simplifying the formula. 40 | */ 41 | class SingleLoopTransformation { 42 | public: 43 | class WitnessBackTranslator { 44 | ChcDirectedGraph const & graph; 45 | TransitionSystem const & transitionSystem; 46 | LocationVarMap locationVarMap; 47 | PositionVarMap positionVarMap; 48 | 49 | public: 50 | WitnessBackTranslator(ChcDirectedGraph const & graph, TransitionSystem const & transitionSystem, 51 | LocationVarMap && locationVarMap, PositionVarMap && positionVarMap) 52 | : graph(graph), 53 | transitionSystem(transitionSystem), 54 | locationVarMap(std::move(locationVarMap)), 55 | positionVarMap(std::move(positionVarMap)) {} 56 | 57 | VerificationResult translate(TransitionSystemVerificationResult result); 58 | 59 | private: 60 | template using ErrorOr = std::variant; 61 | 62 | ErrorOr translateErrorPath(std::size_t unrolling); 63 | 64 | ErrorOr translateInvariant(PTRef inductiveInvariant); 65 | 66 | std::unordered_set getVarsForVertex(SymRef vertex) const; 67 | }; 68 | 69 | // Main method 70 | using TransformationResult = std::pair, std::unique_ptr>; 71 | 72 | TransformationResult transform(ChcDirectedGraph const & graph); 73 | }; 74 | } // namespace golem 75 | 76 | #endif // GOLEM_SINGLELOOPTRANSFORMATION_H 77 | -------------------------------------------------------------------------------- /src/transformers/TransformationPipeline.cc: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (c) 2022-2025, Martin Blicha 4 | * 5 | * SPDX-License-Identifier: MIT 6 | */ 7 | 8 | #include "TransformationPipeline.h" 9 | 10 | namespace golem { 11 | Transformer::TransformationResult TransformationPipeline::transform(std::unique_ptr graph) { 12 | BackTranslator::pipeline_t backtranslators; 13 | for (auto const & transformer : inner) { 14 | auto result = transformer->transform(std::move(graph)); 15 | graph = std::move(result.first); 16 | backtranslators.push_back(std::move(result.second)); 17 | } 18 | std::reverse(backtranslators.begin(), backtranslators.end()); 19 | return {std::move(graph), std::make_unique(std::move(backtranslators))}; 20 | } 21 | 22 | InvalidityWitness TransformationPipeline::BackTranslator::translate(InvalidityWitness witness) { 23 | for (auto const & backtranslator : inner) { 24 | witness = backtranslator->translate(std::move(witness)); 25 | } 26 | return witness; 27 | } 28 | 29 | ValidityWitness TransformationPipeline::BackTranslator::translate(ValidityWitness witness) { 30 | for (auto const & backtranslator : inner) { 31 | witness = backtranslator->translate(std::move(witness)); 32 | } 33 | return witness; 34 | } 35 | } // namespace golem -------------------------------------------------------------------------------- /src/transformers/TransformationPipeline.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_TRANSFORMATIONPIPELINE_H 8 | #define GOLEM_TRANSFORMATIONPIPELINE_H 9 | 10 | #include "Transformer.h" 11 | 12 | namespace golem { 13 | class TransformationPipeline : Transformer { 14 | public: 15 | class BackTranslator : public WitnessBackTranslator { 16 | public: 17 | using pipeline_t = std::vector>; 18 | 19 | BackTranslator(pipeline_t && pipeline) : inner(std::move(pipeline)) {} 20 | 21 | InvalidityWitness translate(InvalidityWitness witness) override; 22 | 23 | ValidityWitness translate(ValidityWitness witness) override; 24 | private: 25 | pipeline_t inner; 26 | }; 27 | 28 | using pipeline_t = std::vector>; 29 | 30 | explicit TransformationPipeline(pipeline_t && pipeline) : inner(std::move(pipeline)) {} 31 | 32 | TransformationResult transform(std::unique_ptr graph) override; 33 | 34 | private: 35 | pipeline_t inner; 36 | }; 37 | } // namespace golem 38 | 39 | #endif //GOLEM_TRANSFORMATIONPIPELINE_H 40 | -------------------------------------------------------------------------------- /src/transformers/Transformer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | 8 | #ifndef GOLEM_TRANSFORMER_H 9 | #define GOLEM_TRANSFORMER_H 10 | 11 | #include "Witnesses.h" 12 | 13 | #include 14 | 15 | namespace golem { 16 | class WitnessBackTranslator { 17 | public: 18 | virtual InvalidityWitness translate(InvalidityWitness witness) = 0; 19 | virtual ValidityWitness translate(ValidityWitness witness) = 0; 20 | virtual ~WitnessBackTranslator() = default; 21 | VerificationResult translate(VerificationResult && result) { 22 | if (not result.hasWitness()) { return std::move(result); } 23 | auto answer = result.getAnswer(); 24 | switch (answer) { 25 | case VerificationAnswer::SAFE: 26 | return {answer, translate(std::move(result).getValidityWitness())}; 27 | case VerificationAnswer::UNSAFE: 28 | return {answer, translate(std::move(result).getInvalidityWitness())}; 29 | case VerificationAnswer::UNKNOWN: 30 | return std::move(result); 31 | } 32 | throw std::logic_error("Unreachable"); 33 | } 34 | }; 35 | 36 | class Transformer { 37 | public: 38 | using TransformationResult = std::pair, std::unique_ptr>; 39 | virtual TransformationResult transform(std::unique_ptr graph) = 0; 40 | virtual ~Transformer() = default; 41 | }; 42 | } // namespace golem 43 | 44 | #endif //GOLEM_TRANSFORMER_H 45 | -------------------------------------------------------------------------------- /src/transformers/TrivialEdgePruner.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "TrivialEdgePruner.h" 8 | 9 | #include "utils/SmtSolver.h" 10 | 11 | namespace golem { 12 | Transformer::TransformationResult TrivialEdgePruner::transform(std::unique_ptr graph) { 13 | std::vector directEdges; 14 | graph->forEachEdge([&](auto const & edge) { 15 | if (edge.to == graph->getExit() and edge.from.size() == 1 and edge.from[0] == graph->getEntry()) { 16 | directEdges.push_back(edge.id); 17 | } 18 | }); 19 | if (directEdges.empty()) { return {std::move(graph), std::make_unique()}; } 20 | Logic & logic = graph->getLogic(); 21 | // Test if any edge is satisfiable 22 | for (EId eid : directEdges) { 23 | SMTSolver solver(logic, SMTSolver::WitnessProduction::NONE); 24 | solver.assertProp(graph->getEdgeLabel(eid)); 25 | auto res = solver.check(); 26 | if (res == SMTSolver::Answer::SAT) { 27 | // satisfiable direct edge, reduce the graph to this edge 28 | NonlinearCanonicalPredicateRepresentation predicateRepresentation(logic); 29 | predicateRepresentation.addRepresentation(graph->getEntry(), {}); 30 | predicateRepresentation.addRepresentation(graph->getExit(), {}); 31 | auto singleEdgeGraph = std::make_unique( 32 | std::vector{graph->getEdge(eid)}, std::move(predicateRepresentation), logic); 33 | return {std::move(singleEdgeGraph), std::make_unique(eid)}; 34 | } 35 | if (res != SMTSolver::Answer::UNSAT) { 36 | // Something went wrong, bail out without any change 37 | return {std::move(graph), std::make_unique()}; 38 | } 39 | } 40 | // Here we know that all direct edges from entry to exit are unsatisfiable, we can safely remove them 41 | graph->deleteEdges(directEdges); 42 | // Nothing to do during backtranslation 43 | return {std::move(graph), std::make_unique()}; 44 | } 45 | 46 | TrivialEdgePruner::BackTranslator::BackTranslator(EId satisfiableEdge) : satisfiableEdge(satisfiableEdge) {} 47 | 48 | InvalidityWitness TrivialEdgePruner::BackTranslator::translate(InvalidityWitness witness) { 49 | if (not satisfiableEdge) return witness; 50 | assert(witness.getDerivation().size() == 2); 51 | witness.getDerivation()[1].clauseId = satisfiableEdge.value(); 52 | return witness; 53 | } 54 | ValidityWitness TrivialEdgePruner::BackTranslator::translate(ValidityWitness witness) { 55 | assert(not satisfiableEdge); 56 | return witness; 57 | } 58 | } // namespace golem 59 | -------------------------------------------------------------------------------- /src/transformers/TrivialEdgePruner.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_TRIVIALEDGEPRUNER_H 8 | #define GOLEM_TRIVIALEDGEPRUNER_H 9 | 10 | #include "Transformer.h" 11 | 12 | namespace golem { 13 | /** 14 | * This transformation removes all direct edges from entry to exit nodes, unless one of such edges has a satisfiable 15 | * label. In such a case, the transformation returns the trivial graph with only this edge. 16 | */ 17 | class TrivialEdgePruner : public Transformer { 18 | class BackTranslator : public WitnessBackTranslator { 19 | public: 20 | BackTranslator() = default; 21 | 22 | explicit BackTranslator(EId satisfiableEdge); 23 | 24 | InvalidityWitness translate(InvalidityWitness witness) override; 25 | 26 | ValidityWitness translate(ValidityWitness witness) override; 27 | 28 | private: 29 | std::optional satisfiableEdge; 30 | }; 31 | 32 | public: 33 | TrivialEdgePruner() = default; 34 | 35 | TransformationResult transform(std::unique_ptr graph) override; 36 | }; 37 | } // namespace golem 38 | 39 | #endif // GOLEM_TRIVIALEDGEPRUNER_H 40 | -------------------------------------------------------------------------------- /src/utils/ScopeGuard.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef SCOPEGUARD_H 8 | #define SCOPEGUARD_H 9 | 10 | #include 11 | 12 | namespace golem { 13 | template 14 | class ScopeGuard { 15 | public: 16 | explicit ScopeGuard(Func&& func) : func(std::forward(func)) {} 17 | 18 | ~ScopeGuard() { func(); } 19 | 20 | ScopeGuard(const ScopeGuard&) = delete; 21 | ScopeGuard& operator=(const ScopeGuard&) = delete; 22 | 23 | private: 24 | Func func; 25 | }; 26 | } // namespace golem 27 | 28 | #endif //SCOPEGUARD_H 29 | -------------------------------------------------------------------------------- /src/utils/SmtSolver.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "SmtSolver.h" 8 | 9 | namespace golem { 10 | SMTSolver::SMTSolver(Logic & logic, WitnessProduction setup) { 11 | bool produceModel = setup == WitnessProduction::ONLY_MODEL || setup == WitnessProduction::MODEL_AND_INTERPOLANTS; 12 | bool produceInterpolants = 13 | setup == WitnessProduction::ONLY_INTERPOLANTS || setup == WitnessProduction::MODEL_AND_INTERPOLANTS; 14 | const char * msg = "ok"; 15 | this->config.setOption(SMTConfig::o_produce_models, SMTOption(produceModel), msg); 16 | this->config.setOption(SMTConfig::o_produce_inter, SMTOption(produceInterpolants), msg); 17 | solver = std::make_unique(logic, config, ""); 18 | } 19 | 20 | void SMTSolver::resetSolver() { 21 | solver = std::make_unique(solver->getLogic(), config, ""); 22 | } 23 | 24 | void SMTSolver::assertProp(PTRef prop) { 25 | solver->insertFormula(prop); 26 | } 27 | 28 | SMTSolver::Answer SMTSolver::check() { 29 | auto res = solver->check(); 30 | if (res == s_True) { return Answer::SAT; } 31 | if (res == s_False) { return Answer::UNSAT; } 32 | if (res == s_Error) { return Answer::ERROR; } 33 | return Answer::UNKNOWN; 34 | } 35 | 36 | void SMTSolver::push() { 37 | solver->push(); 38 | } 39 | 40 | void SMTSolver::pop() { 41 | solver->pop(); 42 | } 43 | 44 | namespace { 45 | Formulas checkEntailmentOneByOne(SMTSolver & solver, Formulas candidates, Logic & logic) { 46 | Formulas implied; 47 | for (PTRef candidate : candidates) { 48 | solver.push(); 49 | solver.assertProp(logic.mkNot(candidate)); 50 | auto const res = solver.check(); 51 | if (res == SMTSolver::Answer::UNSAT) { implied.push(candidate); } 52 | solver.pop(); 53 | } 54 | return implied; 55 | } 56 | 57 | Formulas impliedBy(SMTSolver & solver, Formulas candidates, Logic & logic) { 58 | vec queries; 59 | queries.capacity(candidates.size()); 60 | vec activationLiterals; 61 | activationLiterals.capacity(candidates.size()); 62 | for (int counter = 0; counter < candidates.size(); ++counter) { 63 | std::string name = ".act" + std::to_string(counter); 64 | PTRef activationVariable = logic.mkBoolVar(name.c_str()); 65 | activationLiterals.push(activationVariable); 66 | queries.push(logic.mkAnd(activationLiterals[counter], logic.mkNot(candidates[counter]))); 67 | } 68 | 69 | solver.assertProp(logic.mkOr(queries)); 70 | 71 | auto disabled = 0u; 72 | while (disabled < queries.size_()) { 73 | solver.push(); 74 | solver.assertProp(logic.mkAnd(activationLiterals)); 75 | auto res = solver.check(); 76 | if (res == SMTSolver::Answer::UNSAT) { break; } 77 | if (res != SMTSolver::Answer::SAT) { 78 | assert(false); 79 | throw std::logic_error("Solver could not solve a problem while trying to push components!"); 80 | } 81 | auto model = solver.getModel(); 82 | for (auto i = 0; i < activationLiterals.size(); ++i) { 83 | if (logic.isNot(activationLiterals[i])) { continue; } // already disabled 84 | if (model->evaluate(queries[i]) == logic.getTerm_true()) { 85 | ++disabled; 86 | assert(not logic.isNot(activationLiterals[i])); 87 | activationLiterals[i] = logic.mkNot(activationLiterals[i]); 88 | } 89 | } 90 | solver.pop(); 91 | } 92 | 93 | Formulas implied; 94 | for (auto i = 0; i < candidates.size(); ++i) { 95 | if (not logic.isNot(activationLiterals[i])) { implied.push(candidates[i]); } 96 | } 97 | return implied; 98 | } 99 | } // namespace 100 | 101 | Formulas impliedBy(Formulas candidates, PTRef assertion, Logic & logic) { 102 | SMTSolver solver(logic, SMTSolver::WitnessProduction::ONLY_MODEL); 103 | solver.assertProp(assertion); 104 | return impliedBy(solver, std::move(candidates), logic); 105 | } 106 | 107 | Formulas impliedBy(Formulas candidates, vec const & assertions, Logic & logic) { 108 | SMTSolver solver(logic, SMTSolver::WitnessProduction::ONLY_MODEL); 109 | for (PTRef const assertion : assertions) { 110 | solver.assertProp(assertion); 111 | } 112 | return impliedBy(solver, std::move(candidates), logic); 113 | } 114 | 115 | Formulas checkEntailmentOneByOne(Formulas candidates, PTRef assertion, Logic & logic) { 116 | SMTSolver solver(logic, SMTSolver::WitnessProduction::NONE); 117 | solver.assertProp(assertion); 118 | return checkEntailmentOneByOne(solver, std::move(candidates), logic); 119 | } 120 | 121 | Formulas checkEntailmentOneByOne(Formulas candidates, vec const & assertions, Logic & logic) { 122 | SMTSolver solver(logic, SMTSolver::WitnessProduction::NONE); 123 | for (PTRef const assertion : assertions) { 124 | solver.assertProp(assertion); 125 | } 126 | return checkEntailmentOneByOne(solver, std::move(candidates), logic); 127 | } 128 | } // namespace golem 129 | -------------------------------------------------------------------------------- /src/utils/SmtSolver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023-2024, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_SMTSOLVER_H 8 | #define GOLEM_SMTSOLVER_H 9 | 10 | #include "include/osmt_solver.h" 11 | 12 | namespace golem { 13 | /** 14 | * Simple wrapper around OpenSMT's MainSolver and SMTConfig 15 | */ 16 | class SMTSolver { 17 | std::unique_ptr solver; 18 | SMTConfig config; 19 | 20 | public: 21 | enum class Answer { SAT, UNSAT, UNKNOWN, ERROR }; 22 | 23 | enum class WitnessProduction { NONE, ONLY_MODEL, ONLY_INTERPOLANTS, MODEL_AND_INTERPOLANTS }; 24 | 25 | // Default setup in OpenSMT is currently to produce models, but not interpolants 26 | explicit SMTSolver(Logic & logic, WitnessProduction setup = WitnessProduction::ONLY_MODEL); 27 | 28 | void assertProp(PTRef prop); 29 | 30 | void resetSolver(); 31 | 32 | Answer check(); 33 | 34 | void push(); 35 | void pop(); 36 | 37 | auto getModel() { return solver->getModel(); } 38 | 39 | auto getInterpolationContext() { return solver->getInterpolationContext(); } 40 | 41 | MainSolver & getCoreSolver() { return *solver; } 42 | 43 | SMTConfig & getConfig() { return config; } 44 | }; 45 | 46 | using Formulas = vec; 47 | Formulas impliedBy(Formulas candidates, PTRef assertion, Logic & logic); 48 | Formulas impliedBy(Formulas candidates, vec const & assertions, Logic & logic); 49 | 50 | Formulas checkEntailmentOneByOne(Formulas candidates, PTRef assertion, Logic & logic); 51 | Formulas checkEntailmentOneByOne(Formulas candidates, vec const & assertions, Logic & logic); 52 | } // namespace golem 53 | 54 | #endif // GOLEM_SMTSOLVER_H 55 | -------------------------------------------------------------------------------- /src/utils/StdUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_STDUTILS_H 8 | #define GOLEM_STDUTILS_H 9 | 10 | #include 11 | 12 | namespace golem { 13 | template 14 | std::optional tryGetValue(MapT const & map, typename MapT::key_type const & key) { 15 | auto it = map.find(key); 16 | return it == map.end() ? std::nullopt : std::optional{it->second}; 17 | } 18 | } // namespace golem 19 | 20 | #endif // GOLEM_STDUTILS_H 21 | -------------------------------------------------------------------------------- /src/utils/Timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_TIMER_H 8 | #define GOLEM_TIMER_H 9 | 10 | #include 11 | 12 | namespace golem { 13 | struct Timer { 14 | Timer() : start(std::chrono::high_resolution_clock::now()) {} 15 | [[nodiscard]] auto elapsedMilliseconds() const { 16 | return std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start) 17 | .count(); 18 | } 19 | 20 | private: 21 | std::chrono::high_resolution_clock::time_point start; 22 | }; 23 | } // namespace golem 24 | 25 | #define MEASURE(statement) \ 26 | Timer timer; \ 27 | statement; \ 28 | auto elapsedMs = timer.elapsedMilliseconds(); 29 | 30 | #define MEASURE_AND_REPORT(statement) \ 31 | MEASURE(statement) \ 32 | std::cout << "Execution of \"" << #statement << "\" took " << elapsedMs << " ms\n"; 33 | 34 | #endif // GOLEM_TIMER_H 35 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare( 4 | googletest 5 | GIT_REPOSITORY https://github.com/google/googletest.git 6 | GIT_TAG v1.15.0 7 | ) 8 | 9 | FetchContent_MakeAvailable(googletest) 10 | 11 | include(GoogleTest) 12 | add_executable(GolemTest) 13 | 14 | target_sources(GolemTest 15 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_BMC.cc" 16 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_DAR.cc" 17 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_KIND.cc" 18 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_LAWI.cc" 19 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_MBP.cc" 20 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_NNF.cc" 21 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_Normalizer.cc" 22 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_QE.cc" 23 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_Spacer.cc" 24 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_SymbolicExecution.cc" 25 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_TermUtils.cc" 26 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_TPA.cc" 27 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_TransformationUtils.cc" 28 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_Transformers.cc" 29 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_IMC.cc" 30 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_PDKIND.cc" 31 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/test_PredicateAbstraction.cc" 32 | ) 33 | 34 | target_link_libraries(GolemTest PUBLIC golem_lib gtest gtest_main) 35 | gtest_add_tests(TARGET GolemTest) 36 | -------------------------------------------------------------------------------- /test/TestTemplate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef GOLEM_TESTTEMPLATE_H 8 | #define GOLEM_TESTTEMPLATE_H 9 | 10 | #include "engine/Engine.h" 11 | #include "graph/ChcGraphBuilder.h" 12 | #include "transformers/ConstraintSimplifier.h" 13 | #include "Options.h" 14 | #include "Validator.h" 15 | 16 | #include 17 | 18 | #include 19 | 20 | using namespace golem; 21 | 22 | class EngineTest : public ::testing::Test { 23 | protected: 24 | Options options; 25 | ChcSystem system; 26 | std::unique_ptr logic; 27 | 28 | PTRef mkBoolVar(char const * const name) { return logic->mkBoolVar(name); } 29 | 30 | SRef boolSort() const { return logic->getSort_bool(); } 31 | 32 | SymRef mkPredicateSymbol(std::string const & name, vec const & argSorts) { 33 | SymRef sym = logic->declareFun(name, boolSort(), argSorts); 34 | system.addUninterpretedPredicate(sym); 35 | return sym; 36 | } 37 | 38 | PTRef instantiatePredicate(SymRef symbol, vec const & args) { return logic->mkUninterpFun(symbol, args); } 39 | 40 | void solveSystem(std::vector const & clauses, Engine & engine, VerificationAnswer expectedAnswer, bool validate = true) { 41 | for (auto const & clause : clauses) { system.addClause(clause); } 42 | 43 | Logic & logic = *this->logic; 44 | auto normalizedSystem = Normalizer(logic).normalize(system); 45 | auto hypergraph = ChcGraphBuilder(logic).buildGraph(normalizedSystem); 46 | auto [simplifiedGraph, _] = ConstraintSimplifier{}.transform(std::move(hypergraph)); 47 | auto res = engine.solve(*simplifiedGraph); 48 | auto answer = res.getAnswer(); 49 | ASSERT_EQ(answer, expectedAnswer); 50 | if (validate) { 51 | auto validationResult = Validator(logic).validate(*simplifiedGraph, res); 52 | ASSERT_EQ(validationResult, Validator::Result::VALIDATED); 53 | } 54 | } 55 | }; 56 | 57 | class LIAEngineTest : public EngineTest { 58 | protected: 59 | PTRef zero; 60 | PTRef one; 61 | PTRef two; 62 | PTRef x, xp; 63 | PTRef y, yp; 64 | 65 | LIAEngineTest() { 66 | logic = std::make_unique(opensmt::Logic_t::QF_LIA); 67 | zero = logic->getTerm_IntZero(); 68 | one = logic->getTerm_IntOne(); 69 | two = logic->mkIntConst(2); 70 | x = mkIntVar("x"); 71 | xp = mkIntVar("xp"); 72 | y = mkIntVar("y"); 73 | yp = mkIntVar("yp"); 74 | } 75 | 76 | PTRef mkIntVar(char const * const name) { return logic->mkIntVar(name); } 77 | 78 | SRef intSort() const { return logic->getSort_int(); } 79 | }; 80 | 81 | class LRAEngineTest : public EngineTest { 82 | protected: 83 | PTRef zero; 84 | PTRef one; 85 | PTRef two; 86 | PTRef x, xp; 87 | 88 | LRAEngineTest() { 89 | logic = std::make_unique(opensmt::Logic_t::QF_LRA); 90 | zero = logic->getTerm_RealZero(); 91 | one = logic->getTerm_RealOne(); 92 | two = logic->mkRealConst(2); 93 | x = mkRealVar("x"); 94 | xp = mkRealVar("xp"); 95 | } 96 | 97 | PTRef mkRealVar(char const * const name) { return logic->mkRealVar(name); } 98 | 99 | SRef realSort() const { return logic->getSort_real(); } 100 | }; 101 | 102 | class ALIAEngineTest : public EngineTest { 103 | protected: 104 | PTRef zero; 105 | PTRef one; 106 | PTRef two; 107 | PTRef x, xp; 108 | PTRef y, yp; 109 | PTRef a; 110 | 111 | ALIAEngineTest() { 112 | logic = std::make_unique(opensmt::Logic_t::QF_ALIA); 113 | zero = logic->getTerm_IntZero(); 114 | one = logic->getTerm_IntOne(); 115 | two = logic->mkIntConst(2); 116 | x = mkIntVar("x"); 117 | xp = mkIntVar("xp"); 118 | y = mkIntVar("y"); 119 | yp = mkIntVar("yp"); 120 | a = logic->mkVar(arraySort(), "a", true); 121 | } 122 | 123 | PTRef mkIntVar(char const * const name) { return logic->mkIntVar(name); } 124 | 125 | SRef intSort() const { return logic->getSort_int(); } 126 | SRef arraySort() const { return logic->getArraySort(intSort(), intSort()); } 127 | }; 128 | 129 | #endif //GOLEM_TESTTEMPLATE_H -------------------------------------------------------------------------------- /test/prooftesting/auxiliary-variables.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | 3 | (declare-fun P (Int) Bool) 4 | 5 | (assert (forall ( (x Int) ) (=> (> x 0) (P x)) 6 | )) 7 | 8 | (assert 9 | (forall ( (x Int) (y Int) ) 10 | (=> 11 | (and 12 | (P x) 13 | (and (> x y) (> y 0)) 14 | ) 15 | false 16 | ) 17 | ) 18 | ) 19 | 20 | (check-sat) 21 | (exit) 22 | -------------------------------------------------------------------------------- /test/prooftesting/greater-than-simp.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun Q (Int) Bool) 3 | (assert (forall ((x Int)) (=> (> x 1) (Q x)))) 4 | (assert (forall ((x Int)) (=> (Q x) false))) 5 | (check-sat) 6 | -------------------------------------------------------------------------------- /test/prooftesting/ite.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun Q (Int) Bool) 3 | (assert (forall ((x Int)) (=> (= x 0) (Q x)))) 4 | (assert (forall ((x Int) (c Bool) (d Bool)) (=> (Q x) (Q (ite (and c d) (+ x 1) (- x 1)))))) 5 | (assert (forall ((x Int)) (=> (Q 2) false))) 6 | (check-sat) 7 | -------------------------------------------------------------------------------- /test/prooftesting/let-terms.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | 3 | (declare-fun |INV_MAIN_42| ( Int Int Int Int Int Int Int Int ) Bool) 4 | 5 | (assert 6 | (forall ( (A Int) (B Int) (C Int) (D Int) (E Int) (F Int) (G Int) (H Int) ) 7 | (=> 8 | (and 9 | (and (= E 0) (= D 0) (= C G) (= B 0) (= A F) (= H 0)) 10 | ) 11 | (INV_MAIN_42 A B C D E F G H) 12 | ) 13 | ) 14 | ) 15 | (assert 16 | (forall ( (A Int) (B Int) (C Int) (D Int) (E Int) (F Int) (G Int) (H Int) (I Int) (J Int) (K Int) (L Int) (M Int) ) 17 | (=> 18 | (and 19 | (INV_MAIN_42 A I C J K L G M) 20 | (and (= (+ M L) H) 21 | (= L (+ (- 5) F)) 22 | (not (= K 10)) 23 | (= K (+ (- 1) E)) 24 | (= I (+ (- 1) B)) 25 | (>= (+ C (* (- 1) I)) 1) 26 | (>= (+ G (* (- 1) K)) 1) 27 | (= (+ J (* 5 I) A) D)) 28 | ) 29 | (INV_MAIN_42 A B C D E F G H) 30 | ) 31 | ) 32 | ) 33 | (assert 34 | (forall ( (A Int) (B Int) (C Int) (D Int) (E Int) (F Int) (G Int) (H Int) (I Int) (J Int) (K Int) (L Int) (M Int) ) 35 | (=> 36 | (and 37 | (INV_MAIN_42 A I C J K M G L) 38 | (and (= (+ L M) H) 39 | (= K 10) 40 | (= K (+ (- 1) E)) 41 | (= I (+ (- 1) B)) 42 | (= F 10) 43 | (>= (+ C (* (- 1) I)) 1) 44 | (>= (+ G (* (- 1) K)) 1) 45 | (= (+ J (* 5 I) A) D)) 46 | ) 47 | (INV_MAIN_42 A B C D E F G H) 48 | ) 49 | ) 50 | ) 51 | (assert 52 | (forall ( (A Int) (B Int) (C Int) (D Int) (E Int) (F Int) (G Int) (H Int) (I Int) (J Int) ) 53 | (=> 54 | (and 55 | (INV_MAIN_42 A I C J E F G H) 56 | (let ((a!1 (not (>= (+ G (* (- 1) E)) 1)))) 57 | (and (= I (+ (- 1) B)) a!1 (>= (+ C (* (- 1) I)) 1) (= (+ J (* 5 I) A) D))) 58 | ) 59 | (INV_MAIN_42 A B C D E F G H) 60 | ) 61 | ) 62 | ) 63 | (assert 64 | (forall ( (A Int) (B Int) (C Int) (D Int) (E Int) (F Int) (G Int) (H Int) (I Int) (J Int) (K Int) ) 65 | (=> 66 | (and 67 | (INV_MAIN_42 A B C D I J G K) 68 | (let ((a!1 (not (>= (+ C (* (- 1) B)) 1)))) 69 | (and (= J (+ (- 5) F)) 70 | (not (= I 10)) 71 | (= I (+ (- 1) E)) 72 | a!1 73 | (>= (+ G (* (- 1) I)) 1) 74 | (= (+ K J) H))) 75 | ) 76 | (INV_MAIN_42 A B C D E F G H) 77 | ) 78 | ) 79 | ) 80 | (assert 81 | (forall ( (A Int) (B Int) (C Int) (D Int) (E Int) (F Int) (G Int) (H Int) (I Int) (J Int) (K Int) ) 82 | (=> 83 | (and 84 | (INV_MAIN_42 A B C D I K G J) 85 | (let ((a!1 (not (>= (+ C (* (- 1) B)) 1)))) 86 | (and (= I 10) 87 | (= I (+ (- 1) E)) 88 | (= F 10) 89 | a!1 90 | (>= (+ G (* (- 1) I)) 1) 91 | (= (+ J K) H))) 92 | ) 93 | (INV_MAIN_42 A B C D E F G H) 94 | ) 95 | ) 96 | ) 97 | (assert 98 | (forall ( (A Int) (B Int) (C Int) (D Int) (E Int) (F Int) (G Int) (H Int) ) 99 | (=> 100 | (and 101 | (INV_MAIN_42 G F E A D H C B) 102 | (let ((a!1 (not (>= (+ E (* (- 1) F)) 1))) (a!2 (not (>= (+ C (* (- 1) D)) 1)))) 103 | (and a!1 a!2 (not (= A B)))) 104 | ) 105 | false 106 | ) 107 | ) 108 | ) 109 | 110 | (check-sat) 111 | (exit) 112 | -------------------------------------------------------------------------------- /test/prooftesting/negative_mod.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun Q (Int) Bool) 3 | (assert (forall ((x Int)) (=> (and (< x 0) (= (mod x 3) 1)) (Q x)) 4 | )) 5 | 6 | 7 | (assert (forall ((x Int)) 8 | (=> (Q x) false) 9 | )) 10 | 11 | (check-sat) 12 | -------------------------------------------------------------------------------- /test/prooftesting/non-linearity-three-same-predicates-v2.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun Q (Int) Bool) 3 | (declare-fun P (Int Int) Bool) 4 | 5 | (assert (forall ((x Int) (y Int)) (=> (= x 0) (P x y)) 6 | )) 7 | 8 | 9 | (assert (forall ((x Int) (a0 Int) (a1 Int) (a2 Int) (y Int)) 10 | (=> (and (P x a0) (P x a1) (P x a2) (= y (+ a0 a1 a2))) (Q y)) 11 | )) 12 | 13 | 14 | (assert (forall ((x Int)) 15 | (=> (Q x) false) 16 | )) 17 | 18 | (check-sat) 19 | (exit) 20 | -------------------------------------------------------------------------------- /test/prooftesting/non-linearity-three-same-predicates.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun Q (Int) Bool) 3 | (declare-fun P (Int) Bool) 4 | 5 | (assert (forall ((x Int)) (=> (= x 0) (P x)) 6 | )) 7 | 8 | 9 | (assert (forall ((x Int) (y Int) (z Int) (a Int)) 10 | (=> (and (P x) (P y) (P z) (= a (+ x y z))) (Q a)) 11 | )) 12 | 13 | 14 | (assert (forall ((x Int)) 15 | (=> (Q x) false) 16 | )) 17 | 18 | (check-sat) 19 | (exit) 20 | -------------------------------------------------------------------------------- /test/prooftesting/non-linearity.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun Q (Int) Bool) 3 | (declare-fun P (Int) Bool) 4 | 5 | (assert (forall ((x Int)) (=> (= x 0) (P x)) 6 | )) 7 | 8 | (assert (forall ((x Int)) (=> (= x 0) (Q x)) 9 | )) 10 | 11 | (assert (forall ((x Int) (xp Int)) 12 | (=> (and (Q x) (= xp (+ x 1))) (Q xp)) 13 | )) 14 | 15 | (assert (forall ((x Int) (xp Int)) 16 | (=> (and (P x) (= xp (+ x 1))) (P xp)) 17 | )) 18 | 19 | 20 | (assert (forall ((x Int) (y Int)) 21 | (=> (and (P x) (Q y) (>= x 1) (>= y 1)) false) 22 | )) 23 | 24 | (check-sat) 25 | (exit) 26 | -------------------------------------------------------------------------------- /test/prooftesting/non-normalized_1.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun Q (Int) Bool) 3 | (assert (forall ((x Int)) (=> (= x 0) (Q x))) 4 | ) 5 | (assert (forall ((x Int)) (=> (Q x) (Q (+ x 1)))) 6 | ) 7 | 8 | 9 | (assert (forall ((x Int)) 10 | (=> (and (Q x) (> x 1)) false) 11 | )) 12 | 13 | (check-sat) 14 | -------------------------------------------------------------------------------- /test/prooftesting/non-normalized_2.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun Q (Int Int) Bool) 3 | (assert (forall ((x Int) (y Int)) (=> (> y x) (Q x y))) 4 | ) 5 | 6 | (assert (forall ((x Int)) 7 | (=> (Q x (+ 1 x)) false) 8 | )) 9 | 10 | (check-sat) 11 | -------------------------------------------------------------------------------- /test/prooftesting/predicate_without_args.smt2: -------------------------------------------------------------------------------- 1 | ; ./prepared/hcai/./svcomp/O3/id_o3_false-unreach-call_000.smt2 2 | (set-logic HORN) 3 | 4 | 5 | (declare-fun |main@entry| ( Int ) Bool) 6 | (declare-fun |main@id.exit.split| ( ) Bool) 7 | 8 | (assert 9 | (forall ( (A Int) ) 10 | (=> 11 | (and 12 | true 13 | ) 14 | (main@entry A) 15 | ) 16 | ) 17 | ) 18 | (assert 19 | (forall ( (A Int) ) 20 | (=> 21 | (main@entry A) 22 | main@id.exit.split 23 | ) 24 | ) 25 | ) 26 | (assert 27 | (forall ( (CHC_COMP_UNUSED Bool) ) 28 | (=> 29 | (and 30 | main@id.exit.split 31 | true 32 | ) 33 | false 34 | ) 35 | ) 36 | ) 37 | 38 | (check-sat) 39 | (exit) 40 | -------------------------------------------------------------------------------- /test/prooftesting/proofcheck.py: -------------------------------------------------------------------------------- 1 | from subprocess import call 2 | import os 3 | import sys 4 | 5 | directory = sys.argv[1] 6 | golem_exec = sys.argv[2] 7 | 8 | for smt_file in os.listdir(directory): 9 | 10 | smt_file = directory + "/" + smt_file 11 | proof_file = smt_file + ".proof" 12 | 13 | if not smt_file.endswith(".smt2"): 14 | continue 15 | 16 | with open(proof_file,'w') as f: 17 | call([golem_exec, smt_file, "--print-witness", "--proof-format=alethe"], stdout = f) 18 | 19 | with open(proof_file,'r') as f: 20 | data = f.read().splitlines(True) 21 | 22 | with open(proof_file,'w') as f: 23 | f.writelines(data[1:]) 24 | 25 | ret_code = call(["carcara", "check", proof_file, "--expand-let-bindings"]) 26 | 27 | os.remove(proof_file) 28 | 29 | if ret_code != 0: 30 | print("Check not successful for", smt_file) 31 | 32 | -------------------------------------------------------------------------------- /test/prooftesting/real_constants.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun Q (Real) Bool) 3 | (assert (forall ((x Real)) (=> (> x 1.0) (Q x)))) 4 | (assert (forall ((x Real)) (=> (and (Q x) (= x 2.0)) false))) 5 | (check-sat) 6 | -------------------------------------------------------------------------------- /test/prooftesting/real_negative_constants.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun Q (Real) Bool) 3 | (assert (forall ((x Real)) (=> (> x (- 10.0)) (Q x)))) 4 | (assert (forall ((x Real)) (=> (and (Q x) (= x (- 2.0))) false))) 5 | (check-sat) 6 | -------------------------------------------------------------------------------- /test/prooftesting/two-unused-vars.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun P (Int) Bool) 3 | 4 | (assert (forall ((x Int) (unused Int) (unused2 Bool)) (=> (= x 0) (P x)))) 5 | 6 | (assert (forall ((x Int)) (=> (P x) false))) 7 | 8 | 9 | (check-sat) 10 | (exit) 11 | 12 | -------------------------------------------------------------------------------- /test/prooftesting/unused-qv.smt2: -------------------------------------------------------------------------------- 1 | (set-logic HORN) 2 | (declare-fun Q (Int) Bool) 3 | (declare-fun P (Int) Bool) 4 | 5 | (assert (forall ((x Int) (unused Int)) (=> (= x 0) (P x)))) 6 | 7 | (assert (forall ((x Int)) (=> (= x 0) (Q x)))) 8 | 9 | (assert (forall ((x Int)) (=> (Q x) false))) 10 | 11 | (check-sat) 12 | (exit) 13 | 14 | -------------------------------------------------------------------------------- /test/test_BMC.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "TestTemplate.h" 8 | #include "Validator.h" 9 | #include "engine/Bmc.h" 10 | #include "graph/ChcGraphBuilder.h" 11 | #include 12 | 13 | class BMCTest : public LIAEngineTest { 14 | }; 15 | 16 | TEST(BMC_test, test_BMC_simple) { 17 | ArithLogic logic {opensmt::Logic_t::QF_LIA}; 18 | Options options; 19 | options.addOption(Options::LOGIC, "QF_LIA"); 20 | options.addOption(Options::COMPUTE_WITNESS, "true"); 21 | SymRef s1 = logic.declareFun("s1", logic.getSort_bool(), {logic.getSort_int()}); 22 | PTRef x = logic.mkIntVar("x"); 23 | PTRef xp = logic.mkIntVar("xp"); 24 | PTRef current = logic.mkUninterpFun(s1, {x}); 25 | PTRef next = logic.mkUninterpFun(s1, {xp}); 26 | ChcSystem system; 27 | system.addUninterpretedPredicate(s1); 28 | system.addClause( // x' = 0 => s1(x') 29 | ChcHead{UninterpretedPredicate{next}}, 30 | ChcBody{{logic.mkEq(xp, logic.getTerm_IntZero())}, {}}); 31 | system.addClause( // s1(x) and x' = x + 1 => s1(x') 32 | ChcHead{UninterpretedPredicate{next}}, 33 | ChcBody{{logic.mkEq(xp, logic.mkPlus(x, logic.getTerm_IntOne()))}, {UninterpretedPredicate{current}}} 34 | ); 35 | system.addClause( // s1(x) and x > 1 => false 36 | ChcHead{UninterpretedPredicate{logic.getTerm_false()}}, 37 | ChcBody{{logic.mkGt(x, logic.getTerm_IntOne())}, {UninterpretedPredicate{current}}} 38 | ); 39 | auto normalizedSystem = Normalizer(logic).normalize(system); 40 | auto hypergraph = ChcGraphBuilder(logic).buildGraph(normalizedSystem); 41 | ASSERT_TRUE(hypergraph->isNormalGraph()); 42 | auto graph = hypergraph->toNormalGraph(); 43 | BMC bmc(logic, options); 44 | auto res = bmc.solve(*graph); 45 | auto answer = res.getAnswer(); 46 | ASSERT_EQ(answer, VerificationAnswer::UNSAFE); 47 | auto witness = res.getInvalidityWitness(); 48 | auto validationResult = Validator(logic).validate(*hypergraph, res); 49 | ASSERT_EQ(validationResult, Validator::Result::VALIDATED); 50 | } 51 | 52 | TEST_F(BMCTest, test_BMC_BeyondTransitionSystem) 53 | { 54 | Options options; 55 | options.addOption(Options::LOGIC, "QF_LIA"); 56 | options.addOption(Options::COMPUTE_WITNESS, "true"); 57 | SymRef s1 = mkPredicateSymbol("s1", {intSort(), intSort()}); 58 | SymRef s2 = mkPredicateSymbol("s2", {intSort(), intSort()}); 59 | PTRef current1 = instantiatePredicate(s1, {x,y}); 60 | PTRef next1 = instantiatePredicate(s1, {xp,yp}); 61 | PTRef current2 = instantiatePredicate(s2, {x,y}); 62 | PTRef next2 = instantiatePredicate(s2, {xp,yp}); 63 | // x = 0 and y = 0 => S1(x,y) 64 | // S1(x,y) and x' = x + 1 => S1(x',y) 65 | // S1(x,y) and x > 5 => S2(x,y) 66 | // S2(x,y) and y' = y + 1 => S2(x,y') 67 | // S2(x,y) and y > 5 => S1(x,y) 68 | // S1(x,y) and y > 0 and x = 10 => false 69 | std::vector clauses{ 70 | { 71 | ChcHead{UninterpretedPredicate{next1}}, 72 | ChcBody{{logic->mkAnd(logic->mkEq(xp, zero), logic->mkEq(yp, zero))}, {}} 73 | }, 74 | { 75 | ChcHead{UninterpretedPredicate{next1}}, 76 | ChcBody{{logic->mkAnd(logic->mkEq(xp, logic->mkPlus(x, one)), logic->mkEq(yp, y))}, {UninterpretedPredicate{current1}}} 77 | }, 78 | { 79 | ChcHead{UninterpretedPredicate{current2}}, 80 | ChcBody{{logic->mkGt(x, logic->mkIntConst(5))}, {UninterpretedPredicate{current1}}} 81 | }, 82 | { 83 | ChcHead{UninterpretedPredicate{next2}}, 84 | ChcBody{{logic->mkAnd(logic->mkEq(yp, logic->mkPlus(y, one)), logic->mkEq(xp, x))}, {UninterpretedPredicate{current2}}} 85 | }, 86 | { 87 | ChcHead{UninterpretedPredicate{current1}}, 88 | ChcBody{{logic->mkGt(y, logic->mkIntConst(5))}, {UninterpretedPredicate{current2}}} 89 | }, 90 | { 91 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 92 | ChcBody{{logic->mkAnd(logic->mkGt(y, zero), logic->mkEq(x, logic->mkIntConst(10)))}, {UninterpretedPredicate{current1}}} 93 | }}; 94 | BMC engine(*logic, options); 95 | solveSystem(clauses, engine, VerificationAnswer::UNSAFE, true); 96 | } 97 | 98 | TEST_F(BMCTest, test_KIND_BeyondTransitionSystemWithAuxVar_unsafe) 99 | { 100 | Options options; 101 | options.addOption(Options::LOGIC, "QF_LIA"); 102 | options.addOption(Options::COMPUTE_WITNESS, "true"); 103 | SymRef s1 = mkPredicateSymbol("s1", {intSort(), intSort()}); 104 | SymRef s2 = mkPredicateSymbol("s2", {intSort(), intSort()}); 105 | PTRef current1 = instantiatePredicate(s1, {x,y}); 106 | PTRef next1 = instantiatePredicate(s1, {xp,yp}); 107 | PTRef current2 = instantiatePredicate(s2, {x,y}); 108 | PTRef next2 = instantiatePredicate(s2, {xp,yp}); 109 | PTRef c = logic->mkIntVar("c"); 110 | // x = 0 and y = 0 => S1(x,y) 111 | // S1(x,y) and x' = x + 1 => S1(x',y) 112 | // S1(x,y) and x > 5 => S2(x,y) 113 | // S2(x,y) and y' = y + 1 => S2(x,y') 114 | // S2(x,y) and y > 5 => S1(x,y) 115 | // S1(x,y) and y > c and c > x => false 116 | std::vector clauses{ 117 | { 118 | ChcHead{UninterpretedPredicate{next1}}, 119 | ChcBody{{logic->mkAnd(logic->mkEq(xp, zero), logic->mkEq(yp, zero))}, {}} 120 | }, 121 | { 122 | ChcHead{UninterpretedPredicate{next1}}, 123 | ChcBody{{logic->mkAnd(logic->mkEq(xp, logic->mkPlus(x, one)), logic->mkEq(yp, y))}, {UninterpretedPredicate{current1}}} 124 | }, 125 | { 126 | ChcHead{UninterpretedPredicate{current2}}, 127 | ChcBody{{logic->mkGt(x, logic->mkIntConst(5))}, {UninterpretedPredicate{current1}}} 128 | }, 129 | { 130 | ChcHead{UninterpretedPredicate{next2}}, 131 | ChcBody{{logic->mkAnd(logic->mkEq(yp, logic->mkPlus(y, one)), logic->mkEq(xp, x))}, {UninterpretedPredicate{current2}}} 132 | }, 133 | { 134 | ChcHead{UninterpretedPredicate{current1}}, 135 | ChcBody{{logic->mkGt(y, logic->mkIntConst(5))}, {UninterpretedPredicate{current2}}} 136 | }, 137 | { 138 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 139 | ChcBody{{logic->mkAnd(logic->mkGt(y, c), logic->mkGt(c, x))}, {UninterpretedPredicate{current1}}} 140 | }}; 141 | BMC engine(*logic, options); 142 | solveSystem(clauses, engine, VerificationAnswer::UNSAFE, true); 143 | } 144 | -------------------------------------------------------------------------------- /test/test_DAR.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "TestTemplate.h" 8 | #include "Validator.h" 9 | #include "engine/DAR.h" 10 | #include "graph/ChcGraphBuilder.h" 11 | #include 12 | 13 | class DARTest : public LIAEngineTest { 14 | }; 15 | 16 | TEST_F(DARTest, test_DAR_simple_safe) 17 | { 18 | options.addOption(Options::COMPUTE_WITNESS, "true"); 19 | SymRef s1 = mkPredicateSymbol("s1", {intSort()}); 20 | PTRef current = instantiatePredicate(s1, {x}); 21 | PTRef next = instantiatePredicate(s1, {xp}); 22 | std::vector clauses{ 23 | { 24 | ChcHead{UninterpretedPredicate{next}}, 25 | ChcBody{{logic->mkEq(xp, zero)}, {}}}, 26 | { 27 | ChcHead{UninterpretedPredicate{next}}, 28 | ChcBody{{logic->mkEq(xp, logic->mkPlus(x, one))}, {UninterpretedPredicate{current}}} 29 | }, 30 | { 31 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 32 | ChcBody{{logic->mkLt(x, zero)}, {UninterpretedPredicate{current}}} 33 | }}; 34 | DAR engine(*logic, options); 35 | solveSystem(clauses, engine, VerificationAnswer::SAFE, true); 36 | } 37 | 38 | TEST_F(DARTest, test_DAR_simple_unsafe) 39 | { 40 | options.addOption(Options::COMPUTE_WITNESS, "true"); 41 | SymRef s1 = mkPredicateSymbol("s1",{intSort()}); 42 | PTRef current = instantiatePredicate(s1, {x}); 43 | PTRef next = instantiatePredicate(s1, {xp}); 44 | std::vector clauses{{ 45 | ChcHead{UninterpretedPredicate{next}}, 46 | ChcBody{{logic->mkEq(xp, zero)}, {}}}, 47 | { 48 | ChcHead{UninterpretedPredicate{next}}, 49 | ChcBody{{logic->mkEq(xp, logic->mkPlus(x, one))}, {UninterpretedPredicate{current}}} 50 | }, 51 | { 52 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 53 | ChcBody{{logic->mkGt(x, two)}, {UninterpretedPredicate{current}}} 54 | }}; 55 | DAR engine(*logic, options); 56 | solveSystem(clauses, engine, VerificationAnswer::UNSAFE, true); 57 | } -------------------------------------------------------------------------------- /test/test_KIND.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "TestTemplate.h" 8 | #include "engine/Kind.h" 9 | 10 | class KindTest : public LIAEngineTest { 11 | }; 12 | 13 | TEST_F(KindTest, test_KIND_simple_safe) 14 | { 15 | options.addOption(Options::LOGIC, "QF_LIA"); 16 | options.addOption(Options::COMPUTE_WITNESS, "true"); 17 | SymRef s1 = mkPredicateSymbol("s1", {intSort()}); 18 | PTRef current = instantiatePredicate(s1, {x}); 19 | PTRef next = instantiatePredicate(s1, {xp}); 20 | // x = 0 => S1(x) 21 | // S1(x) and x' = x + 1 => S1(x') 22 | // S1(x) and x < 0 => false 23 | std::vector clauses{ 24 | { 25 | ChcHead{UninterpretedPredicate{next}}, 26 | ChcBody{{logic->mkEq(xp, zero)}, {}} 27 | }, 28 | { 29 | ChcHead{UninterpretedPredicate{next}}, 30 | ChcBody{{logic->mkEq(xp, logic->mkPlus(x, one))}, {UninterpretedPredicate{current}}} 31 | }, 32 | { 33 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 34 | ChcBody{{logic->mkLt(x, zero)}, {UninterpretedPredicate{current}}} 35 | }}; 36 | Kind engine(*logic, options); 37 | solveSystem(clauses, engine, VerificationAnswer::SAFE, true); 38 | } 39 | 40 | TEST_F(KindTest, test_KIND_moreInductionForward_safe) 41 | { 42 | options.addOption(Options::LOGIC, "QF_LIA"); 43 | options.addOption(Options::COMPUTE_WITNESS, "true"); 44 | SymRef s1 = mkPredicateSymbol("s1", {intSort()}); 45 | PTRef current = instantiatePredicate(s1, {x}); 46 | PTRef next = instantiatePredicate(s1, {xp}); 47 | // x = 0 => S1(x) 48 | // S1(x) and x' = ite(x = 10, 0, x + 1) => S1(x') 49 | // S1(x) and x = 15 => false 50 | std::vector clauses{ 51 | { 52 | ChcHead{UninterpretedPredicate{next}}, 53 | ChcBody{{logic->mkEq(xp, zero)}, {}} 54 | }, 55 | { 56 | ChcHead{UninterpretedPredicate{next}}, 57 | ChcBody{{logic->mkEq(xp, logic->mkIte(logic->mkEq(x, logic->mkIntConst(10)), zero, logic->mkPlus(x, one)))}, {UninterpretedPredicate{current}}} 58 | }, 59 | { 60 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 61 | ChcBody{{logic->mkEq(x, logic->mkIntConst(15))}, {UninterpretedPredicate{current}}} 62 | }}; 63 | Kind engine(*logic, options); 64 | solveSystem(clauses, engine, VerificationAnswer::SAFE, true); 65 | } 66 | 67 | TEST_F(KindTest, test_KIND_moreInductionBackward_safe) 68 | { 69 | Options options; 70 | options.addOption(Options::LOGIC, "QF_LIA"); 71 | options.addOption(Options::COMPUTE_WITNESS, "true"); 72 | SymRef s1 = mkPredicateSymbol("s1", {intSort()}); 73 | PTRef current = instantiatePredicate(s1, {x}); 74 | PTRef next = instantiatePredicate(s1, {xp}); 75 | // x = 0 => S1(x) 76 | // S1(x) and x' = ite(x = 5, 0, x + 1) => S1(x') 77 | // S1(x) and x = 15 => false 78 | std::vector clauses{ 79 | { 80 | ChcHead{UninterpretedPredicate{next}}, 81 | ChcBody{{logic->mkEq(xp, zero)}, {}} 82 | }, 83 | { 84 | ChcHead{UninterpretedPredicate{next}}, 85 | ChcBody{{logic->mkEq(xp, logic->mkIte(logic->mkEq(x, logic->mkIntConst(5)), zero, logic->mkPlus(x, one)))}, {UninterpretedPredicate{current}}} 86 | }, 87 | { 88 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 89 | ChcBody{{logic->mkEq(x, logic->mkIntConst(15))}, {UninterpretedPredicate{current}}} 90 | }}; 91 | Kind engine(*logic, options); 92 | solveSystem(clauses, engine, VerificationAnswer::SAFE, true); 93 | } -------------------------------------------------------------------------------- /test/test_NNF.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include 8 | #include "TermUtils.h" 9 | 10 | #include "utils/SmtSolver.h" 11 | 12 | using namespace golem; 13 | 14 | namespace{ 15 | bool implies(Logic & logic, PTRef antecedent, PTRef consequent) { 16 | SMTSolver solver(logic); 17 | solver.assertProp(logic.mkNot(logic.mkImpl(antecedent, consequent))); 18 | auto res = solver.check(); 19 | return res == SMTSolver::Answer::UNSAT; 20 | } 21 | } 22 | 23 | 24 | class NNFTest : public ::testing::Test { 25 | protected: 26 | ArithLogic logic {opensmt::Logic_t::QF_LRA}; 27 | PTRef x; 28 | PTRef b; 29 | PTRef a; 30 | PTRef c; 31 | PTRef na; 32 | PTRef nb; 33 | PTRef nc; 34 | PTRef zero; 35 | PTRef one; 36 | NNFTest() { 37 | x = logic.mkRealVar("x"); 38 | a = logic.mkBoolVar("a"); 39 | b = logic.mkBoolVar("b"); 40 | c = logic.mkBoolVar("c"); 41 | na = logic.mkNot(a); 42 | nb = logic.mkNot(b); 43 | nc = logic.mkNot(c); 44 | zero = logic.getTerm_RealZero(); 45 | one = logic.getTerm_RealOne(); 46 | } 47 | }; 48 | 49 | TEST_F(NNFTest, test_Atom) { 50 | PTRef atom = logic.mkLeq(x, zero); 51 | PTRef nnf = TermUtils(logic).toNNF(atom); 52 | ASSERT_EQ(atom, nnf); 53 | } 54 | 55 | TEST_F(NNFTest, test_NegatedConjunction) { 56 | PTRef atom = logic.mkLeq(x, zero); 57 | PTRef conj = logic.mkAnd(atom, b); 58 | PTRef fla = logic.mkNot(conj); 59 | PTRef nnf = TermUtils(logic).toNNF(fla); 60 | ASSERT_EQ(nnf, logic.mkOr(logic.mkNot(atom), logic.mkNot(b))); 61 | } 62 | 63 | TEST_F(NNFTest, test_NegatedDisjunction) { 64 | PTRef atom = logic.mkLeq(x, zero); 65 | PTRef disj = logic.mkOr(atom, b); 66 | PTRef fla = logic.mkNot(disj); 67 | PTRef nnf = TermUtils(logic).toNNF(fla); 68 | ASSERT_EQ(nnf, logic.mkAnd(logic.mkNot(atom), logic.mkNot(b))); 69 | } 70 | 71 | TEST_F(NNFTest, test_BoolEquality) { 72 | PTRef eq = logic.mkEq(a,b); 73 | PTRef nnf = TermUtils(logic).toNNF(eq); 74 | ASSERT_NE(nnf, eq); 75 | EXPECT_EQ(nnf, logic.mkAnd( 76 | logic.mkOr(a, logic.mkNot(b)), 77 | logic.mkOr(b, logic.mkNot(a)) 78 | )); 79 | } 80 | 81 | TEST_F(NNFTest, test_NestedNegatedDisjunction) { 82 | // (= (= a b) c) && (a || ~b) 83 | 84 | PTRef eq = logic.mkEq(a, b); 85 | PTRef eq2 = logic.mkEq(eq, c); 86 | PTRef disj = logic.mkOr(a, nb); 87 | PTRef fla = logic.mkAnd(eq2, disj); 88 | PTRef nnf = TermUtils(logic).toNNF(fla); 89 | 90 | ASSERT_TRUE(implies(logic, fla, nnf)); 91 | ASSERT_TRUE(implies(logic, nnf, fla)); 92 | } -------------------------------------------------------------------------------- /test/test_Normalizer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include 8 | 9 | #include "Normalizer.h" 10 | #include "graph/ChcGraphBuilder.h" 11 | 12 | using namespace golem; 13 | 14 | TEST(NormalizerTest, test_boolean_equal_to_constant) { 15 | ArithLogic logic {opensmt::Logic_t::QF_LIA}; 16 | 17 | SymRef s1 = logic.declareFun("s1", logic.getSort_bool(), {logic.getSort_int(), logic.getSort_bool()}); 18 | PTRef x = logic.mkIntVar("x"); 19 | PTRef b = logic.mkBoolVar("b"); 20 | ChcSystem system; 21 | system.addUninterpretedPredicate(s1); 22 | 23 | system.addClause( 24 | ChcHead{UninterpretedPredicate{logic.mkUninterpFun(s1, {logic.getTerm_IntZero(), logic.getTerm_false()})}}, 25 | ChcBody{{logic.getTerm_true()}, {}} 26 | ); 27 | 28 | system.addClause( 29 | ChcHead{UninterpretedPredicate{logic.mkUninterpFun(s1, {x, logic.getTerm_true()})}}, 30 | ChcBody{InterpretedFla{logic.mkEq(b, logic.getTerm_false())}, {UninterpretedPredicate{logic.mkUninterpFun(s1, {x, b})}}} 31 | ); 32 | 33 | system.addClause( 34 | ChcHead{logic.getTerm_false()}, 35 | ChcBody{InterpretedFla{}, {UninterpretedPredicate{logic.mkUninterpFun(s1, {logic.getTerm_IntZero(), logic.getTerm_true()})}}} 36 | ); 37 | ChcPrinter(logic, std::cout).print(system); 38 | auto normalizedSystem = Normalizer(logic).normalize(system); 39 | ChcPrinter(logic, std::cout).print(*normalizedSystem.normalizedSystem); 40 | auto graph = ChcGraphBuilder(logic).buildGraph(normalizedSystem)->toNormalGraph(); 41 | graph->toDot(std::cout); 42 | } 43 | 44 | TEST(NormalizerTest, test_ModConstraint) { 45 | ArithLogic logic {opensmt::Logic_t::QF_LIA}; 46 | 47 | SymRef s1 = logic.declareFun("s1", logic.getSort_bool(), {logic.getSort_int()}); 48 | PTRef x = logic.mkIntVar("x"); 49 | PTRef c = logic.mkIntVar("c"); 50 | PTRef d = logic.mkIntVar("d"); 51 | PTRef zero = logic.getTerm_IntZero(); 52 | ChcSystem system; 53 | system.addUninterpretedPredicate(s1); 54 | 55 | system.addClause( 56 | ChcHead{UninterpretedPredicate{logic.mkUninterpFun(s1, {x})}}, 57 | ChcBody{{logic.mkAnd(logic.mkEq(c, zero), logic.mkEq(x, logic.mkPlus(c,logic.mkTimes(d, logic.mkIntConst(3)))))}, {}} 58 | ); 59 | system.addClause( 60 | ChcHead{UninterpretedPredicate{logic.getTerm_false()}}, 61 | ChcBody{{logic.mkEq(zero, logic.mkMod(x,logic.mkIntConst(2)))}, {UninterpretedPredicate{logic.mkUninterpFun(s1, {x})}}} 62 | ); 63 | ChcPrinter(logic, std::cout).print(system); 64 | auto normalizedSystem = Normalizer(logic).normalize(system); 65 | auto const & clauses = normalizedSystem.normalizedSystem->getClauses(); 66 | ChcPrinter(logic, std::cout).print(*normalizedSystem.normalizedSystem); 67 | ASSERT_NE(clauses[0].body.interpretedPart.fla, logic.getTerm_true()); 68 | // auto graph = ChcGraphBuilder(logic).buildGraph(normalizedSystem)->toNormalGraph(logic); 69 | // graph->toDot(std::cout, logic); 70 | } 71 | -------------------------------------------------------------------------------- /test/test_QE.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include 8 | #include 9 | #include "QuantifierElimination.h" 10 | 11 | using namespace golem; 12 | 13 | class QE_RealTest : public ::testing::Test { 14 | protected: 15 | ArithLogic logic {opensmt::Logic_t::QF_LRA}; 16 | PTRef x; 17 | PTRef y; 18 | PTRef z; 19 | PTRef a; 20 | PTRef b; 21 | PTRef c; 22 | PTRef zero; 23 | PTRef one; 24 | QE_RealTest() { 25 | x = logic.mkRealVar("x"); 26 | y = logic.mkRealVar("y"); 27 | z = logic.mkRealVar("z"); 28 | a = logic.mkBoolVar("a"); 29 | b = logic.mkBoolVar("b"); 30 | c = logic.mkBoolVar("c"); 31 | zero = logic.getTerm_RealZero(); 32 | one = logic.getTerm_RealOne(); 33 | } 34 | }; 35 | 36 | TEST_F(QE_RealTest, test_singleVar_Equality) { 37 | PTRef fla = logic.mkEq(y, x); 38 | QuantifierElimination qe(logic); 39 | PTRef res = qe.eliminate(fla, x); 40 | EXPECT_EQ(res, logic.getTerm_true()); 41 | fla = logic.mkAnd(fla, logic.mkEq(x, zero)); 42 | res = qe.eliminate(fla, x); 43 | std::cout << logic.printTerm(res) << std::endl; 44 | EXPECT_TRUE(res == logic.mkEq(y, zero) or res == logic.mkAnd(logic.mkLeq(y, zero), logic.mkGeq(y, zero))); 45 | } 46 | 47 | TEST_F(QE_RealTest, test_singleBoolVar) { 48 | /* 49 | * F = (and (or a b) (or (not a) c) 50 | * after elimination of a: (or b c) 51 | */ 52 | PTRef fla = logic.mkAnd( 53 | logic.mkOr(a,b), 54 | logic.mkOr(logic.mkNot(a),c) 55 | ); 56 | QuantifierElimination qe(logic); 57 | PTRef res = qe.eliminate(fla, a); 58 | // std::cout << logic.printTerm(res) << std::endl; 59 | EXPECT_EQ(res, logic.mkOr(b,c)); 60 | } 61 | 62 | TEST_F(QE_RealTest, test_strictInequalities) { 63 | PTRef lit1 = logic.mkLeq(zero, x); 64 | PTRef lit2 = logic.mkLeq(x, logic.mkMinus(y, one)); 65 | PTRef lit3 = logic.mkGeq(x, logic.mkMinus(y, one)); 66 | PTRef lit4 = logic.mkNot(logic.mkEq(y, one)); 67 | PTRef fla = logic.mkAnd({lit1, lit2, lit3, lit4}); 68 | PTRef res = QuantifierElimination(logic).eliminate(fla, y); 69 | std::cout << logic.printTerm(res) << std::endl; 70 | // EXPECT_EQ(res, logic.mkNumLt(zero, x)); 71 | // The result is equivalent to x > 0, but we are missing arithmetic simplifications to get it to that form 72 | // Current result is x >= 0 and x > 0 which is equivalent to x > 0; 73 | EXPECT_EQ(res, logic.mkAnd(logic.mkLt(zero, x), logic.mkLeq(zero, x))); 74 | } 75 | 76 | class TrivialQE_IntTest : public ::testing::Test { 77 | protected: 78 | ArithLogic logic {opensmt::Logic_t::QF_ALIA}; 79 | PTRef x; 80 | PTRef y; 81 | PTRef xp; 82 | PTRef yp; 83 | PTRef zero; 84 | PTRef one; 85 | TrivialQE_IntTest() : 86 | x {logic.mkIntVar("x")}, 87 | y {logic.mkIntVar("y")}, 88 | xp {logic.mkIntVar("xp")}, 89 | yp {logic.mkIntVar("yp")}, 90 | zero {logic.getTerm_IntZero()}, 91 | one {logic.getTerm_IntOne()} 92 | { } 93 | }; 94 | 95 | TEST_F(TrivialQE_IntTest, test_TwoIncrementedVariables) { 96 | PTRef base = logic.mkEq(x,y); 97 | PTRef inc1 = logic.mkEq(xp, logic.mkPlus(x, one)); 98 | PTRef inc2 = logic.mkEq(yp, logic.mkPlus(y, one)); 99 | PTRef fla = logic.mkAnd({base, inc1, inc2}); 100 | PTRef res = TrivialQuantifierElimination(logic).tryEliminateVarsExcept(vec{xp, yp}, fla); 101 | // std::cout << logic.printTerm(res) << std::endl; 102 | EXPECT_EQ(res, logic.mkEq(xp, yp)); 103 | } 104 | 105 | TEST_F(TrivialQE_IntTest, test_TwoDecrementedVariables) { 106 | PTRef base = logic.mkEq(x,y); 107 | PTRef dec1 = logic.mkEq(xp, logic.mkMinus(x, one)); 108 | PTRef dec2 = logic.mkEq(yp, logic.mkMinus(y, one)); 109 | PTRef fla = logic.mkAnd({base, dec1, dec2}); 110 | PTRef res = TrivialQuantifierElimination(logic).tryEliminateVarsExcept(vec{xp, yp}, fla); 111 | // std::cout << logic.printTerm(res) << std::endl; 112 | EXPECT_EQ(res, logic.mkEq(xp, yp)); 113 | } 114 | 115 | TEST_F(TrivialQE_IntTest, test_BooleanVaribles) { 116 | PTRef b1 = logic.mkBoolVar("b1"); 117 | PTRef b2 = logic.mkBoolVar("b2"); 118 | PTRef fla = logic.mkEq(b1,b2); 119 | PTRef res1 = TrivialQuantifierElimination(logic).tryEliminateVarsExcept(vec{b1}, fla); 120 | EXPECT_EQ(res1, logic.getTerm_true()); 121 | PTRef res2 = TrivialQuantifierElimination(logic).tryEliminateVarsExcept(vec{b2}, fla); 122 | EXPECT_EQ(res2, logic.getTerm_true()); 123 | } 124 | 125 | TEST_F(TrivialQE_IntTest, test_ShiftedVarAndArrayAccess) { 126 | SRef const arraySort = logic.getArraySort(logic.getSort_int(), logic.getSort_int()); 127 | PTRef const s1 = logic.mkVar(arraySort, "s1"); 128 | PTRef const s2 = logic.mkIntVar("s2"); 129 | PTRef const a1 = logic.mkIntVar("a1"); 130 | PTRef const a2 = logic.mkIntVar("a2"); 131 | PTRef const fla = logic.mkAnd({logic.mkEq(a1,a2), logic.mkEq(logic.mkPlus(s2, one), a1), logic.mkEq(logic.mkSelect({s1, a2}), one)}); 132 | PTRef const res = TrivialQuantifierElimination(logic).tryEliminateVarsExcept(vec{s1,s2}, fla); 133 | EXPECT_EQ(res, logic.mkEq(logic.mkSelect({s1, logic.mkPlus(s2, one)}), one)); 134 | } -------------------------------------------------------------------------------- /test/test_SymbolicExecution.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "TestTemplate.h" 8 | #include "Validator.h" 9 | #include "engine/SymbolicExecution.h" 10 | #include "graph/ChcGraphBuilder.h" 11 | #include 12 | 13 | class SETest : public LIAEngineTest { 14 | }; 15 | 16 | TEST_F(SETest, test_Simple_Unsafe) { 17 | options.addOption(Options::COMPUTE_WITNESS, "true"); 18 | SymRef inv_sym = mkPredicateSymbol("Inv", {intSort()}); 19 | PTRef inv = instantiatePredicate(inv_sym, {x}); 20 | PTRef invp = instantiatePredicate(inv_sym, {xp}); 21 | std::vector clauses{ 22 | { // x' = 0 => Inv(x') 23 | ChcHead{UninterpretedPredicate{invp}}, 24 | ChcBody{{logic->mkEq(xp, zero)}, {}} 25 | }, 26 | { // Inv(x) & x' = x + 1 => Inv(x') 27 | ChcHead{UninterpretedPredicate{invp}}, 28 | ChcBody{{logic->mkEq(xp, logic->mkPlus(x, one))}, {UninterpretedPredicate{inv}}} 29 | }, 30 | { // Inv(x) & x > 1 => false 31 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 32 | ChcBody{{logic->mkGt(x, one)}, {UninterpretedPredicate{inv}}} 33 | } 34 | }; 35 | 36 | SymbolicExecution engine(*logic, options); 37 | solveSystem(clauses, engine, VerificationAnswer::UNSAFE); 38 | } 39 | 40 | TEST_F(SETest, test_Simple_Safe) { 41 | options.addOption(Options::COMPUTE_WITNESS, "true"); 42 | SymRef inv_sym = mkPredicateSymbol("Inv", {intSort(), intSort()}); 43 | PTRef inv = instantiatePredicate(inv_sym, {x, y}); 44 | PTRef invp = instantiatePredicate(inv_sym, {xp, yp}); 45 | std::vector clauses{ 46 | { // x' = y' => Inv(x', y') 47 | ChcHead{UninterpretedPredicate{invp}}, 48 | ChcBody{{logic->mkEq(xp, yp)}, {}} 49 | }, 50 | { // Inv(x,y) and x' = x + 1 and y' = y + 1 => Inv(x', y') 51 | ChcHead{UninterpretedPredicate{invp}}, 52 | ChcBody{{logic->mkAnd(logic->mkEq(xp, logic->mkPlus(x, one)), logic->mkEq(yp, logic->mkPlus(y, one)))}, {UninterpretedPredicate{inv}}} 53 | }, 54 | { // Inv(x, y) and x != y => false 55 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 56 | ChcBody{{logic->mkNot(logic->mkEq(x, y))}, {UninterpretedPredicate{inv}}} 57 | } 58 | }; 59 | 60 | SymbolicExecution engine(*logic, options); 61 | solveSystem(clauses, engine, VerificationAnswer::SAFE); 62 | } 63 | 64 | TEST_F(SETest, test_BeyondTransitionSystem_Unsafe) 65 | { 66 | options.addOption(Options::COMPUTE_WITNESS, "true"); 67 | SymRef s1 = mkPredicateSymbol("s1", {intSort(), intSort()}); 68 | SymRef s2 = mkPredicateSymbol("s2", {intSort(), intSort()}); 69 | PTRef current1 = instantiatePredicate(s1, {x,y}); 70 | PTRef next1 = instantiatePredicate(s1, {xp,yp}); 71 | PTRef current2 = instantiatePredicate(s2, {x,y}); 72 | PTRef next2 = instantiatePredicate(s2, {xp,yp}); 73 | // x = 0 and y = 0 => S1(x,y) 74 | // S1(x,y) and x' = x + 1 => S1(x',y) 75 | // S1(x,y) and x > 5 => S2(x,y) 76 | // S2(x,y) and y' = y + 1 => S2(x,y') 77 | // S2(x,y) and y > 5 => S1(x,y) 78 | // S1(x,y) and y > 0 and x = 10 => false 79 | std::vector clauses{ 80 | { 81 | ChcHead{UninterpretedPredicate{next1}}, 82 | ChcBody{{logic->mkAnd(logic->mkEq(xp, zero), logic->mkEq(yp, zero))}, {}} 83 | }, 84 | { 85 | ChcHead{UninterpretedPredicate{next1}}, 86 | ChcBody{{logic->mkAnd(logic->mkEq(xp, logic->mkPlus(x, one)), logic->mkEq(yp, y))}, {UninterpretedPredicate{current1}}} 87 | }, 88 | { 89 | ChcHead{UninterpretedPredicate{current2}}, 90 | ChcBody{{logic->mkGt(x, logic->mkIntConst(5))}, {UninterpretedPredicate{current1}}} 91 | }, 92 | { 93 | ChcHead{UninterpretedPredicate{next2}}, 94 | ChcBody{{logic->mkAnd(logic->mkEq(yp, logic->mkPlus(y, one)), logic->mkEq(xp, x))}, {UninterpretedPredicate{current2}}} 95 | }, 96 | { 97 | ChcHead{UninterpretedPredicate{current1}}, 98 | ChcBody{{logic->mkGt(y, logic->mkIntConst(5))}, {UninterpretedPredicate{current2}}} 99 | }, 100 | { 101 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 102 | ChcBody{{logic->mkAnd(logic->mkGt(y, zero), logic->mkEq(x, logic->mkIntConst(10)))}, {UninterpretedPredicate{current1}}} 103 | }}; 104 | SymbolicExecution engine(*logic, options); 105 | solveSystem(clauses, engine, VerificationAnswer::UNSAFE); 106 | } -------------------------------------------------------------------------------- /test/test_TermUtils.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include 8 | #include "TermUtils.h" 9 | 10 | using namespace golem; 11 | 12 | bool contains(vec const & v, PTRef p) { 13 | for (PTRef t : v) { 14 | if (p == t) { return true; } 15 | } 16 | return false; 17 | } 18 | 19 | class TermUtils_Test : public ::testing::Test { 20 | protected: 21 | ArithLogic logic {opensmt::Logic_t::QF_LRA}; 22 | TermUtils utils {logic}; 23 | PTRef a; 24 | PTRef b; 25 | PTRef c; 26 | PTRef na; 27 | PTRef nb; 28 | PTRef nc; 29 | 30 | TermUtils_Test() { 31 | a = logic.mkBoolVar("a"); 32 | b = logic.mkBoolVar("b"); 33 | c = logic.mkBoolVar("c"); 34 | na = logic.mkNot(a); 35 | nb = logic.mkNot(b); 36 | nc = logic.mkNot(c); 37 | } 38 | }; 39 | 40 | TEST_F(TermUtils_Test, test_TopLevelConjuncts_SingleTerm) { 41 | PTRef fla = a; 42 | auto conjunctions = utils.getTopLevelConjuncts(fla); 43 | ASSERT_EQ(conjunctions.size(), 1); 44 | EXPECT_EQ(conjunctions[0], a); 45 | } 46 | 47 | TEST_F(TermUtils_Test, test_TopLevelConjuncts_SingleNegatedTerm) { 48 | PTRef fla = logic.mkNot(a); 49 | auto conjunctions = utils.getTopLevelConjuncts(fla); 50 | ASSERT_EQ(conjunctions.size(), 1); 51 | EXPECT_EQ(conjunctions[0], fla); 52 | } 53 | 54 | TEST_F(TermUtils_Test, test_TopLevelConjuncts_SingleDisjunction) { 55 | PTRef fla = logic.mkOr(a, b); 56 | auto conjunctions = utils.getTopLevelConjuncts(fla); 57 | ASSERT_EQ(conjunctions.size(), 1); 58 | EXPECT_EQ(conjunctions[0], fla); 59 | } 60 | 61 | TEST_F(TermUtils_Test, test_TopLevelConjuncts_NestedOneLevel) { 62 | PTRef disj = logic.mkOr(a,b); 63 | PTRef fla = logic.mkNot(disj); 64 | auto conjunctions = utils.getTopLevelConjuncts(fla); 65 | ASSERT_EQ(conjunctions.size(), 2); 66 | EXPECT_TRUE(contains(conjunctions, na)); 67 | EXPECT_TRUE(contains(conjunctions, nb)); 68 | } 69 | 70 | TEST_F(TermUtils_Test, test_TopLevelConjuncts_NestedTwoLevels) { 71 | PTRef nested = logic.mkOr(a,b); 72 | PTRef fla = logic.mkNot(logic.mkOr(nested, c)); 73 | auto conjunctions = utils.getTopLevelConjuncts(fla); 74 | ASSERT_EQ(conjunctions.size(), 3); 75 | EXPECT_TRUE(contains(conjunctions, na)); 76 | EXPECT_TRUE(contains(conjunctions, nb)); 77 | EXPECT_TRUE(contains(conjunctions, nc)); 78 | } 79 | 80 | TEST_F(TermUtils_Test, test_TopLevelDisjuncts_SingleTerm) { 81 | PTRef fla = a; 82 | auto disjunctions = utils.getTopLevelDisjuncts(fla); 83 | ASSERT_EQ(disjunctions.size(), 1); 84 | EXPECT_EQ(disjunctions[0], a); 85 | } 86 | 87 | TEST_F(TermUtils_Test, test_TopLevelDisjuncts_SingleNegatedTerm) { 88 | PTRef fla = logic.mkNot(a); 89 | auto disjunctions = utils.getTopLevelDisjuncts(fla); 90 | ASSERT_EQ(disjunctions.size(), 1); 91 | EXPECT_EQ(disjunctions[0], fla); 92 | } 93 | 94 | TEST_F(TermUtils_Test, test_TopLevelDisjuncts_SingleConjunction) { 95 | PTRef fla = logic.mkAnd(a, b); 96 | auto disjunctions = utils.getTopLevelDisjuncts(fla); 97 | ASSERT_EQ(disjunctions.size(), 1); 98 | EXPECT_EQ(disjunctions[0], fla); 99 | } 100 | 101 | TEST_F(TermUtils_Test, test_TopLevelDisjuncts_NestedOneLevel) { 102 | PTRef disj = logic.mkAnd(a,b); 103 | PTRef fla = logic.mkNot(disj); 104 | auto disjunctions = utils.getTopLevelDisjuncts(fla); 105 | ASSERT_EQ(disjunctions.size(), 2); 106 | EXPECT_TRUE(contains(disjunctions, na)); 107 | EXPECT_TRUE(contains(disjunctions, nb)); 108 | } 109 | 110 | TEST_F(TermUtils_Test, test_TopLevelDisjuncts_NestedTwoLevels) { 111 | PTRef nested = logic.mkAnd(a,b); 112 | PTRef fla = logic.mkNot(logic.mkAnd(nested, c)); 113 | auto disjunctions = utils.getTopLevelDisjuncts(fla); 114 | ASSERT_EQ(disjunctions.size(), 3); 115 | EXPECT_TRUE(contains(disjunctions, na)); 116 | EXPECT_TRUE(contains(disjunctions, nb)); 117 | EXPECT_TRUE(contains(disjunctions, nc)); 118 | } -------------------------------------------------------------------------------- /test/test_TransformationUtils.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023-2025, Martin Blicha 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #include "TestTemplate.h" 8 | #include "TransformationUtils.h" 9 | #include 10 | 11 | using namespace golem; 12 | 13 | class TransformationUtils_Test : public LIAEngineTest { 14 | }; 15 | 16 | TEST_F(TransformationUtils_Test, test_DAGWithExtraEdge) { 17 | SymRef s1 = mkPredicateSymbol("s1", {intSort()}); 18 | PTRef current = instantiatePredicate(s1, {x}); 19 | PTRef next = instantiatePredicate(s1, {xp}); 20 | std::vector clauses{ 21 | { 22 | ChcHead{UninterpretedPredicate{next}}, 23 | ChcBody{{logic->mkEq(xp, zero)}, {}}}, 24 | { 25 | ChcHead{UninterpretedPredicate{next}}, 26 | ChcBody{{logic->mkEq(xp, logic->mkPlus(x, one))}, {UninterpretedPredicate{current}}} 27 | }, 28 | { 29 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 30 | ChcBody{{logic->mkLt(x, zero)}, {UninterpretedPredicate{current}}} 31 | }, 32 | { 33 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 34 | ChcBody{{logic->mkLt(y, zero)}, {}} 35 | } 36 | }; 37 | for (auto const & clause : clauses) { system.addClause(clause); } 38 | 39 | Logic & logic = *this->logic; 40 | auto normalizedSystem = Normalizer(logic).normalize(system); 41 | auto hypergraph = ChcGraphBuilder(logic).buildGraph(normalizedSystem); 42 | ASSERT_TRUE(hypergraph->isNormalGraph()); 43 | auto graph = hypergraph->toNormalGraph(); 44 | EXPECT_FALSE(isTransitionSystem(*graph)); 45 | EXPECT_FALSE(isTransitionSystemDAG(*graph)); // Because of the extra edge from entry to exit 46 | } 47 | 48 | TEST_F(TransformationUtils_Test, test_TransitionSystemWithUnusedStateVariableInTransiotion) { 49 | SymRef s1 = mkPredicateSymbol("s1", {intSort(), intSort()}); 50 | PTRef current = instantiatePredicate(s1, {x, y}); 51 | PTRef next = instantiatePredicate(s1, {xp, yp}); 52 | std::vector clauses{ 53 | { 54 | ChcHead{UninterpretedPredicate{next}}, 55 | ChcBody{{logic->mkEq(xp, zero)}, {}}}, 56 | { 57 | ChcHead{UninterpretedPredicate{next}}, 58 | ChcBody{{logic->mkEq(xp, logic->mkPlus(x, one))}, {UninterpretedPredicate{current}}} 59 | }, 60 | { 61 | ChcHead{UninterpretedPredicate{logic->getTerm_false()}}, 62 | ChcBody{{logic->mkAnd(logic->mkLt(x, zero), logic->mkEq(y, zero))}, {UninterpretedPredicate{current}}} 63 | } 64 | }; 65 | for (auto const & clause : clauses) { system.addClause(clause); } 66 | 67 | Logic & logic = *this->logic; 68 | auto normalizedSystem = Normalizer(logic).normalize(system); 69 | auto hypergraph = ChcGraphBuilder(logic).buildGraph(normalizedSystem); 70 | ASSERT_TRUE(hypergraph->isNormalGraph()); 71 | auto graph = hypergraph->toNormalGraph(); 72 | ASSERT_TRUE(isTransitionSystem(*graph)); 73 | auto ts = toTransitionSystem(*graph); 74 | ASSERT_EQ(ts->getStateVars().size(), 2); 75 | } 76 | 77 | --------------------------------------------------------------------------------