├── templates ├── post_bindings.js ├── pre_bindings.js ├── pre_ref_bindings.js └── post_ref_bindings.js ├── test ├── scripts ├── binds ├── setup ├── copy_bindings ├── build_wrapper ├── pull_z3 ├── build_bindings ├── build_z3 ├── build_ref_bindings ├── get_emscripten └── postinstall ├── src ├── Tests │ ├── Bespoke.js │ ├── ExtractionTest.js │ ├── RegexUnitTests.js │ └── Z3Test.js ├── Z3Loader.js ├── Check.js ├── Z3Utils.js ├── Z3.js ├── Model.js ├── Query.js ├── Solver.js ├── Expr.js ├── Context.js └── Regex.js ├── .gitignore ├── .travis.yml ├── .eslintrc.json ├── README.md ├── package.json └── LICENSE /templates/post_bindings.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/pre_bindings.js: -------------------------------------------------------------------------------- 1 | var GeneratedBindings = []; 2 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | ./scripts/build_wrapper 2 | node bin/Tests/Z3Test.js 3 | exit $? 4 | -------------------------------------------------------------------------------- /scripts/binds: -------------------------------------------------------------------------------- 1 | #Copyright Blake Loring 2015 2 | ./scripts/build_bindings 3 | ./scripts/build_ref_bindings 4 | -------------------------------------------------------------------------------- /scripts/setup: -------------------------------------------------------------------------------- 1 | #Copyright Blake Loring 2015 2 | 3 | echo "Building Wrapper" 4 | ./scripts/build_wrapper 5 | 6 | exit 0 7 | -------------------------------------------------------------------------------- /scripts/copy_bindings: -------------------------------------------------------------------------------- 1 | #Copyright Blake Loring 2015 2 | 3 | echo "Copying Bindings" 4 | cp ./z3/src/api/python/z3_bindings* ./bin/ 5 | echo "Done" -------------------------------------------------------------------------------- /src/Tests/Bespoke.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import Test from './Z3Test.js'; 4 | import Z3 from '../Z3.js'; 5 | 6 | Test(/^..$/); 7 | 8 | console.log(Z3.Query.TOTAL); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | z3/ 2 | emsdk_portable/ 3 | node_modules/ 4 | bin/libz3* 5 | bin/ 6 | #Backup files 7 | *~ 8 | 9 | # Debug files 10 | *.dSYM/ 11 | **/.DS_Store 12 | package-lock.json 13 | -------------------------------------------------------------------------------- /scripts/build_wrapper: -------------------------------------------------------------------------------- 1 | #Copyright Blake Loring 2015 2 | 3 | # Cleaning sources 4 | echo "Cleaning Sources" 5 | npx eslint --fix .eslintrc.json **/src/**.js 6 | npx babel --presets "@babel/preset-env" -d bin/ src/ 7 | -------------------------------------------------------------------------------- /scripts/pull_z3: -------------------------------------------------------------------------------- 1 | #Copyright Blake Loring 2015 2 | 3 | if [ -e z3 ]; then 4 | echo "Dont need to pull Z3" 5 | else 6 | git clone https://github.com/ExpoSEJS/z3.git 7 | cd z3 8 | git checkout 8f3b923 9 | fi 10 | -------------------------------------------------------------------------------- /src/Z3Loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | */ 4 | const Z3Loader = require("./package"); 5 | const Z3Path = process && process.env.Z3_PATH ? process.env.Z3_PATH : undefined; 6 | export default Z3Loader(Z3Path); 7 | -------------------------------------------------------------------------------- /scripts/build_bindings: -------------------------------------------------------------------------------- 1 | #Copyright Blake Loring 2015 2 | 3 | entry="$(cat ./templates/pre_bindings.js)" 4 | binding="$(cat ./bin/z3_bindings_stripped.js)" 5 | exit="$(cat ./templates/post_bindings.js)" 6 | 7 | printf "$entry \n\n$binding \n\n$exit" > ./bin/z3_bindings_built.js -------------------------------------------------------------------------------- /scripts/build_z3: -------------------------------------------------------------------------------- 1 | #Copyright Blake Loring 2015 2 | 3 | cd z3 4 | 5 | if [ -e build ]; then 6 | rm -rf ./build/ 7 | fi 8 | 9 | python3 scripts/mk_make.py 10 | (cd ../ && ./scripts/copy_bindings) 11 | cd build 12 | 13 | make -j 16 14 | cp libz3* ../../bin/ 15 | -------------------------------------------------------------------------------- /scripts/build_ref_bindings: -------------------------------------------------------------------------------- 1 | #Copyright Blake Loring 2015 2 | entry="$(cat ./templates/pre_ref_bindings.js)" 3 | binding="$(cat ./bin/z3_bindings_built.js)" 4 | exit="$(cat ./templates/post_ref_bindings.js)" 5 | printf "$entry \n\n$binding \n\n$exit" > ./bin/z3_bindings_ref.js 6 | -------------------------------------------------------------------------------- /scripts/get_emscripten: -------------------------------------------------------------------------------- 1 | #Copyright Blake Loring 2015 2 | 3 | rm -f emsdk-portable.tar.gz 4 | curl -O https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz 5 | tar xzf emsdk-portable.tar.gz 6 | rm -f emsdk-portable.tar.gz 7 | cd emsdk_portable 8 | 9 | ./emsdk update 10 | ./emsdk install latest 11 | ./emsdk activate latest 12 | 13 | -------------------------------------------------------------------------------- /src/Check.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 3 | */ 4 | 5 | export default function(check_predicate, altgen) { 6 | return function(query, model) { 7 | 8 | //Returns the Query check structure with either a list of alternative queries or nothing 9 | let sat = check_predicate(model); 10 | 11 | return { 12 | isSAT: sat, 13 | alternatives: !sat ? altgen(query, model) : [] 14 | }; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/Z3Utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | */ 4 | 5 | 6 | /** 7 | * Recursively reduce Expr to AST 8 | */ 9 | function astArray(args) { 10 | return args.map(a => { 11 | 12 | if (a instanceof Array) { 13 | return astArray(a); 14 | } 15 | 16 | return a.ast || a; 17 | }); 18 | } 19 | 20 | export default { 21 | wrap: function(ctx, ret) { 22 | return ret; 23 | }, 24 | astArray: astArray 25 | }; 26 | -------------------------------------------------------------------------------- /scripts/postinstall: -------------------------------------------------------------------------------- 1 | #Copyright Blake Loring 2015 2 | 3 | #Necessary as npm doesnt want to copy .PHONY and bin/ 4 | if [ -e bin ]; then 5 | echo "no bin dir" 6 | else 7 | mkdir bin 8 | fi 9 | 10 | ./scripts/pull_z3 11 | echo "DllBuild" 12 | ./scripts/build_z3 13 | 14 | echo "Building Bindings" 15 | ./scripts/binds 16 | 17 | echo "Bindings Cp" 18 | cp ./bin/z3_bindings_ref.js ./bin/package.js 19 | 20 | echo "Done Z3JS" 21 | #./scripts/setup 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | 3 | matrix: 4 | include: 5 | - os: linux 6 | addons: 7 | apt: 8 | sources: 9 | - llvm-toolchain-trusty-5.0 10 | packages: 11 | - clang-5.0 12 | env: 13 | - MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0" 14 | 15 | before_install: 16 | - eval "${MATRIX_EVAL}" 17 | 18 | language: node_js 19 | node_js: "lts/*" 20 | 21 | install: npm install --clang=1 22 | script: npm test 23 | -------------------------------------------------------------------------------- /src/Tests/ExtractionTest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | */ 4 | "use strict"; 5 | 6 | import Test from './Z3Test.js'; 7 | import Z3 from '../Z3.js'; 8 | 9 | const ctx = new Z3.Context(); 10 | const solver = new Z3.Solver(ctx); 11 | 12 | let arrayInstance = ctx.mkObject('Obj', ctx.mkStringSort()); 13 | 14 | let forceSelect = ctx.mkEq(ctx.mkSelect(arrayInstance, ctx.mkString('What')), ctx.mkString('Bob Jenkins')); 15 | arrayInstance.addField(ctx.mkString('What')); 16 | 17 | solver.assert(forceSelect); 18 | 19 | let mdl = solver.getModel(); 20 | console.log(arrayInstance.asConstant(mdl)); 21 | 22 | process.exit(0); 23 | -------------------------------------------------------------------------------- /src/Z3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | */ 4 | 5 | 6 | 7 | /** 8 | * A node.js API for Z3. Currently, all objects only increment ref counts but never decrement. 9 | * Ideally, a garbage collector would call a finalizer on the objects that then decrements the 10 | * ref counts. 11 | */ 12 | 13 | import Expr from "./Expr"; 14 | import Model from "./Model"; 15 | import Context from "./Context.js"; 16 | import Solver from "./Solver"; 17 | import Regex from "./Regex"; 18 | import Query from "./Query"; 19 | import Check from "./Check"; 20 | 21 | let API = {}; 22 | 23 | API.Solver = Solver; 24 | API.Context = Context; 25 | API.Model = Model; 26 | API.Expr = Expr; 27 | API.Regex = Regex; 28 | API.Query = Query; 29 | API.Check = Check; 30 | 31 | export default API; -------------------------------------------------------------------------------- /src/Model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | */ 4 | 5 | import Z3 from "./Z3Loader"; 6 | import Expr from "./Expr"; 7 | 8 | class Model { 9 | constructor(context, model) { 10 | this.context = context; 11 | this.mdl = model; 12 | Z3.Z3_model_inc_ref(this.context.ctx, this.mdl); 13 | } 14 | 15 | toString() { 16 | return Z3.Z3_model_to_string(this.context.ctx, this.mdl); 17 | } 18 | 19 | eval(expr) { 20 | let res = Z3.bindings_model_eval(this.context.ctx, this.mdl, expr.ast); 21 | //TODO: Propogating array lengths like this is horrible, find a better way 22 | return res ? (new Expr(this.context, res)).setLength(expr.getLength()).setFields(expr.getFields()) : null; 23 | } 24 | 25 | destroy() { 26 | Z3.Z3_model_dec_ref(this.context.ctx, this.mdl); 27 | } 28 | } 29 | 30 | export default Model; 31 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "env": { 4 | "node": true, 5 | "es6": true, 6 | "commonjs": true 7 | }, 8 | 9 | "extends": "eslint:recommended", 10 | 11 | "parserOptions": { 12 | "ecmaVersion": 6, 13 | "sourceType": "module" 14 | }, 15 | 16 | "rules": { 17 | 18 | "indent": [ 19 | "error", 20 | "tab" 21 | ], 22 | 23 | "linebreak-style": [ 24 | "error", 25 | "unix" 26 | ], 27 | 28 | "no-useless-escape": [ 29 | "warn" 30 | ], 31 | 32 | "quotes": [ 33 | "warn", 34 | "double" 35 | ], 36 | 37 | "semi": [ 38 | "error", 39 | "always" 40 | ], 41 | 42 | "strict": [ 43 | "error", 44 | "safe" 45 | ], 46 | 47 | "no-unused-vars": [2, {"args": "after-used", "argsIgnorePattern": "^_"}], 48 | 49 | "no-console":"off" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/ExpoSEJS/z3javascript.svg?branch=master)](https://travis-ci.org/ExpoSEJS/z3javascript) 2 | 3 | ## Z3Javascript 4 | 5 | A project to try and get Z3 running as a Javascript library. The project exercises a custom Z3 build through the library `ref`. 6 | 7 | NOTE: `Z3_PATH` environment variable should point to the Z3 .so .dll or .dylib. This dll or dylib file will be automatically created and moved to bin when the library is `npm install`'d 8 | 9 | ## Usage 10 | 11 | Used as an npm dependency. 12 | 13 | `npm install git+ssh://github.com/ExpoSEJS/z3javascript.git` 14 | 15 | Z3 will be automatically downloaded and built. We require clang to build Z3, 16 | 17 | From then, simply point the `Z3_PATH` environment variable to the generated dll, so, etc using something like `Z3_PATH=./node_modules/z3javascript/bin/libz3.dylib node myapplication.js` to use your program. View tests for an example. 18 | 19 | ## Dependencies 20 | 21 | build-essential, clang (5.0 onward) and Node.js and NPM (Use the latest LTS) 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "z3javascript", 3 | "version": "0.0.1", 4 | "description": "ExpoSE: Z3 Prover bindings for JavaScript", 5 | "main": "./bin/Z3.js", 6 | "dependencies": { 7 | "eslint": "^5.16.0", 8 | "@makeomatic/ffi-napi": "^4.1.0", 9 | "node-gyp": "^10.1.0", 10 | "ref-napi": "^3.0.2", 11 | "ref-array-napi": "^1.2.2" 12 | }, 13 | "devDependencies": { 14 | "@babel/cli": "^7.4.4", 15 | "@babel/core": "^7.4.5", 16 | "@babel/preset-env": "^7.4.5" 17 | }, 18 | "scripts": { 19 | "prepublish": "./scripts/setup", 20 | "postinstall": "./scripts/postinstall", 21 | "build": "./scripts/postinstall; ./scripts/setup", 22 | "test": "Z3_PATH=./bin/libz3 ./test" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/ExpoSEJS/z3javascript.git" 27 | }, 28 | "author": "Blake Loring ", 29 | "license": "MIT (./LICENSE)", 30 | "bugs": { 31 | "url": "https://github.com/ExpoSEJS/z3javascript/issues" 32 | }, 33 | "homepage": "https://github.com/ExpoSEJS/z3javascript#readme" 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | z3javascript 2 | 3 | All rights reserved. 4 | 5 | Copyright (c) 2015 Blake Loring , Johannes Kinder 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /src/Query.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 3 | */ 4 | class Query { 5 | constructor(exprs, checks) { 6 | this.exprs = exprs; 7 | this.checks = checks; 8 | } 9 | 10 | getModel(solver) { 11 | return Query.process(solver, [this]); 12 | } 13 | } 14 | 15 | Query.MAX_REFINEMENTS = -1; 16 | Query.TOTAL = 0; 17 | Query.LAST_ATTEMPTS = 0; 18 | 19 | Query.canAttempt = function(currentAttempts) { 20 | return Query.MAX_REFINEMENTS == -1 || (currentAttempts < Query.MAX_REFINEMENTS); 21 | }; 22 | 23 | Query.process = function(solver, alternatives) { 24 | let attempts = 0; 25 | let model = null; 26 | 27 | while (Query.canAttempt(attempts) && alternatives.length) { 28 | attempts++; 29 | Query.TOTAL++; 30 | 31 | let next = alternatives.splice(Math.floor(Math.random() * alternatives.length), 1)[0]; 32 | 33 | solver.push(); 34 | 35 | next.exprs.forEach(clause => solver.assert(clause)); 36 | model = solver.getModel(); 37 | 38 | solver.pop(); 39 | 40 | if (model) { 41 | //Run all the checks and concat any alternatives 42 | const all_checks = next.checks.map(check => check(next, model)).filter(x => !!x); 43 | alternatives = all_checks.reduce((alt, next) => alt.concat(next.alternatives), alternatives); 44 | 45 | //Find any failing check 46 | const failed_check = all_checks.find(check => !check.isSAT); 47 | 48 | //If we have found a satisfying model return it otherwise add alternatives from check 49 | if (failed_check) { 50 | model.destroy(); 51 | model = null; 52 | } else { 53 | break; 54 | } 55 | } 56 | } 57 | 58 | Query.LAST_ATTEMPTS = attempts; 59 | return model; 60 | }; 61 | 62 | export default Query; 63 | -------------------------------------------------------------------------------- /src/Tests/RegexUnitTests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | */ 4 | "use strict"; 5 | 6 | import Test from './Z3Test.js'; 7 | import Z3 from '../Z3.js'; 8 | 9 | let errorCount = 0; 10 | 11 | function Assert(regex, given, expectedCaptures, not) { 12 | console.log('Process' + regex); 13 | let before = Z3.Query.TOTAL; 14 | let res = Test(regex); 15 | if (res === 'GOOD') { 16 | console.log(`Generated good model for ${regex} with ${Z3.Query.TOTAL - before} queries`); 17 | } else { 18 | console.log(`Error in ${regex} ${res}`); 19 | process.exit(1); 20 | errorCount++; 21 | } 22 | } 23 | 24 | Assert(/[0-9]{3}/); 25 | Assert(/[0-9]{undefined}/); 26 | 27 | Assert(/xxx/); 28 | Assert(/(xxx)/); 29 | 30 | Assert(/(x)(y)(z)/); 31 | Assert(/(x)((yz)(z))/); 32 | 33 | //Tests on klene* 34 | Assert(/^(a)*$/, '', ['', '']); 35 | Assert(/^(a)*$/); 36 | Assert(/^(a?)*$/); 37 | Assert(/^(a?)*$/); 38 | Assert(/^(([a-z]+)a)*$/); 39 | Assert(/^((([a-z]+)d)ef)*$/); 40 | 41 | //Tests on klene+ 42 | Assert(/^([a-z]+)$/); 43 | Assert(/^([a-zA-Z]+)([a-z]{3})$/); 44 | Assert(/^([a-zA-Z]+)(([a-z]){3})$/); 45 | Assert(/^([a-zA-Z]+)+(([a-z]){3})$/); 46 | 47 | //Tests on strings with preceeding matches 48 | Assert(/xyz/); 49 | Assert(/a+/); 50 | Assert(/[a-z]+[a-z]{5}/); 51 | 52 | Assert(/^([a-zA-Z])+ef$/); 53 | Assert(/^([a-zA-Z]){5}ef$/); 54 | 55 | //Loop tests on captures 56 | Assert(/^([a-zA-Z]?){5}$/); 57 | 58 | //Real world ones 59 | Assert(/^abc$|^$/); 60 | Assert(/^((?:<|>)?=?)\s*([v=\s]*([0-9]+)\.([0-9]+)\.([0-9]+))$/); 61 | Assert(/^((?:<|>)?=?)\s*([v=\s]*([0-9]+)\.([0-9]+)\.([0-9]+)(?:-?((?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*)(?:\.(?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?)$/); 62 | 63 | 64 | console.log(`Exit with ${errorCount} errors`); 65 | 66 | process.exit(errorCount); 67 | -------------------------------------------------------------------------------- /templates/pre_ref_bindings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | */ 4 | "use strict"; 5 | 6 | // The core of the node.js API for Z3. This file could be mostly autogenerated by Z3 scripts. Currently 7 | // only the function bindings are, not the types 8 | var ref = require('ref-napi'); 9 | var ArrayType = require('ref-array-napi'); 10 | 11 | module.exports = function(libPath) { 12 | 13 | // Manually defined types (from Z3 Python API). Could possibly be simplified to just Voidp 14 | // but maybe we'll need the distinction later 15 | var Void = ref.types.void, 16 | Voidp = ref.refType(Void); 17 | 18 | var Z3Exception = Voidp; 19 | var ContextObj = Voidp; 20 | var OptimizeObj = Voidp; 21 | var TheoryObj = Voidp; 22 | var Config = Voidp; 23 | var Symbol = Voidp; 24 | var Sort = Voidp; 25 | var FuncDecl = Voidp; 26 | var Ast = Voidp; 27 | var Pattern = Voidp; 28 | var Model = Voidp; 29 | var Literals = Voidp; 30 | var Constructor = Voidp; 31 | var ConstructorList = Voidp; 32 | var GoalObj = Voidp; 33 | var TacticObj = Voidp; 34 | var ProbeObj = Voidp; 35 | var ApplyResultObj = Voidp; 36 | var StatsObj = Voidp; 37 | var SolverObj = Voidp; 38 | var FixedpointObj = Voidp; 39 | var ModelObj = Voidp; 40 | var AstVectorObj = Voidp; 41 | var AstMapObj = Voidp; 42 | var Params = Voidp; 43 | var ParamDescrs = Voidp; 44 | var FuncInterpObj = Voidp; 45 | var FuncEntryObj = Voidp; 46 | var RCFNumObj = Voidp; 47 | var SolverCallbackObj = Voidp; 48 | 49 | // Names for standard types 50 | var CUInt = ref.types.uint32; 51 | var CInt = ref.types.int32; 52 | var CBool = ref.types.bool; 53 | var CFloat = ref.types.float; 54 | var CULong = ref.types.uint64; 55 | var CLong = ref.types.int64; 56 | var CDouble = ref.types.double; 57 | var CChar = ref.types.char; 58 | var CString = ref.types.CString; 59 | 60 | // Array types. Not all of these may be valid (check over time) 61 | var AstArray = ArrayType(Ast); 62 | var CIntArray = ArrayType(CInt); 63 | var CUIntArray = ArrayType(CUInt); 64 | var SymbolArray = ArrayType(Symbol); 65 | var SortArray = ArrayType(Symbol); 66 | var FuncDeclArray = ArrayType(FuncDecl); 67 | var ConstructorArray = ArrayType(Constructor); 68 | var ConstructorListArray = ArrayType(ConstructorList); 69 | var PatternArray = ArrayType(Pattern); 70 | var TacticObjArray = ArrayType(TacticObj); 71 | var RCFNumObjArray = ArrayType(RCFNumObj); 72 | -------------------------------------------------------------------------------- /src/Solver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | */ 4 | 5 | import Z3 from "./Z3Loader"; 6 | import Model from "./Model"; 7 | 8 | class Solver { 9 | 10 | constructor(context, incremental, options) { 11 | options = options || []; 12 | this.context = context; 13 | 14 | let config = Z3.Z3_mk_params(this.context.ctx); 15 | Z3.Z3_params_inc_ref(this.context.ctx, config); 16 | 17 | options.forEach(option => { 18 | if (typeof(option.value) === "number") { 19 | Z3.Z3_params_set_uint(this.context.ctx, config, Z3.Z3_mk_string_symbol(this.context.ctx, option.name), option.value); 20 | } else if (typeof(option.value) === "string") { 21 | Z3.Z3_params_set_symbol(this.context.ctx, config, Z3.Z3_mk_string_symbol(this.context.ctx, option.name), Z3.Z3_mk_string_symbol(this.context.ctx, option.value)); 22 | } 23 | }); 24 | 25 | if (incremental) { 26 | this.slv = Z3.Z3_mk_simple_solver(this.context.ctx); 27 | } else { 28 | let defaultTactic = Z3.Z3_mk_tactic(this.context.ctx, "default"); 29 | Z3.Z3_tactic_inc_ref(this.context.ctx, defaultTactic); 30 | this.slv = Z3.Z3_mk_solver_from_tactic(this.context.ctx, defaultTactic); 31 | } 32 | 33 | Z3.Z3_solver_inc_ref(this.context.ctx, this.slv); 34 | Z3.Z3_solver_set_params(this.context.ctx, this.slv, config); 35 | } 36 | 37 | destroy() { 38 | Z3.Z3_solver_dec_ref(this.context.ctx, this.slv); 39 | } 40 | 41 | reset() { 42 | Z3.Z3_solver_reset(this.context.ctx, this.slv); 43 | } 44 | 45 | push() { 46 | Z3.Z3_solver_push(this.context.ctx, this.slv); 47 | } 48 | 49 | pop() { 50 | Z3.Z3_solver_pop(this.context.ctx, this.slv, 1); 51 | } 52 | 53 | check() { 54 | return Z3.Z3_solver_check(this.context.ctx, this.slv) === Z3.TRUE; 55 | } 56 | 57 | /** 58 | * Process an SMT2Lib string and assert it on slv 59 | */ 60 | fromString(str) { 61 | Z3.Z3_solver_from_string(this.context.ctx, this.slv, str); 62 | } 63 | 64 | getModel() { 65 | if (this.check()) { 66 | return new Model(this.context, Z3.Z3_solver_get_model(this.context.ctx, this.slv)); 67 | } else { 68 | return null; 69 | } 70 | } 71 | 72 | assert(expr) { 73 | return Z3.Z3_solver_assert(this.context.ctx, this.slv, expr.ast); 74 | } 75 | 76 | toString() { 77 | return "Solver {\n" + Z3.Z3_solver_to_string(this.context.ctx, this.slv) + "}"; 78 | } 79 | } 80 | 81 | export default Solver; 82 | -------------------------------------------------------------------------------- /src/Tests/Z3Test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | */ 4 | "use strict"; 5 | 6 | import Z3 from '../Z3'; 7 | 8 | const ctx = new Z3.Context(); 9 | const solver = new Z3.Solver(ctx, false, []); 10 | 11 | function Test(Origin) { 12 | solver.reset(); 13 | 14 | let TestRegex = Z3.Regex(ctx, Origin); 15 | let symbolic = ctx.mkConst(ctx.mkStringSymbol('TestC0'), ctx.mkStringSort()); 16 | 17 | //Assert to make capture correct all the time solver.assert(ctx.mkEq(TestRegex.captures[1], ctx.mkString(''))); 18 | //solver.assert(ctx.mkEq(TestRegex.captures[1], ctx.mkString('a'))); 19 | solver.assert(ctx.mkSeqInRe(symbolic, TestRegex.ast)); 20 | solver.assert(ctx.mkImplies(ctx.mkSeqInRe(symbolic, TestRegex.ast), ctx.mkEq(symbolic, TestRegex.implier))); 21 | 22 | TestRegex.assertions.forEach(assert => { 23 | solver.assert(assert); 24 | }); 25 | 26 | function Exists(array1, array2, pred) { 27 | 28 | for (let i = 0; i < array1.length; i++) { 29 | if (pred(array1[i], array2[i])) { 30 | return true; 31 | } 32 | } 33 | 34 | return false; 35 | } 36 | 37 | function DoesntMatch(l, r) { 38 | if (l === undefined) { 39 | return r !== ''; 40 | } else { 41 | return l !== r; 42 | } 43 | } 44 | 45 | function CheckCorrect(model) { 46 | const real_match = Origin.exec(model.eval(symbolic).asConstant()); 47 | const sym_match = TestRegex.captures.map(cap => model.eval(cap).asConstant()); 48 | const matches = real_match && !Exists(real_match, sym_match, DoesntMatch); 49 | console.log('Matches:', matches, model.eval(symbolic).asConstant(), Origin); 50 | return matches; 51 | } 52 | 53 | let NotMatch = Z3.Check(CheckCorrect, (query, model) => { 54 | let query_list = query.exprs.concat([ctx.mkNot(ctx.mkEq(symbolic, ctx.mkString(model.eval(symbolic).asConstant())))]); 55 | return new Z3.Query(query_list, query.checks); 56 | }); 57 | 58 | let CheckFixed = Z3.Check(CheckCorrect, (query, model) => { 59 | let real_match = Origin.exec(model.eval(symbolic).asConstant()); 60 | 61 | if (!real_match) { 62 | return []; 63 | } else { 64 | 65 | real_match = Origin.exec(model.eval(symbolic).asConstant()).map(match => match || ''); 66 | console.log(`Here ${real_match.length} in ${TestRegex.captures.length}`); 67 | 68 | TestRegex.captures.forEach((x, idx) => { 69 | console.log(`${x} => ${real_match[idx]}`); 70 | }); 71 | 72 | let query_list = TestRegex.captures.map((cap, idx) => ctx.mkEq(ctx.mkString(real_match[idx]), cap)); 73 | return [new Z3.Query(query.exprs.concat(query_list), [Z3.Check(CheckCorrect, (query, model) => [])])]; 74 | } 75 | 76 | }); 77 | 78 | let query = new Z3.Query([], [CheckFixed, NotMatch]); 79 | let mdl = query.getModel(solver); 80 | 81 | if (mdl) { 82 | let match = Origin.exec(mdl.eval(symbolic).asConstant()); 83 | console.log('Modelled Match: ' + mdl.eval(symbolic).asConstant()); 84 | 85 | if (match) { 86 | console.log(`Model: ${mdl.eval(symbolic).asConstant()} Captures: ${JSON.stringify(match)}`); 87 | console.log('Match Length: ' + match.length + ' CapturesLength: ' + TestRegex.captures.length); 88 | return CheckCorrect(mdl) ? 'GOOD' : 'BAD CAPTURE'; 89 | } else { 90 | return 'BAD MATCH'; 91 | } 92 | } else { 93 | return 'UNSAT'; 94 | } 95 | } 96 | 97 | const test_re = [/hello/, /[0-9]{3,}/, /[0-9]{undefined}/, /[0-9]{5}/, /(?!hi)hello/, /(?=hello)hello/, /(?=[12345])./, /webkit|android|google/, /(?:webkit)?google/, /^\bGiggles$/, /^(.*)\1(Hello)\2$/, /^([12345]+)\1$/, /^Hello.\bWorld$/, /^<(.+)>.+<\1>$/, /(Capture)\1/, /^\bGiggles\b$/, /^((?!chrome|android).)*safari/i]; 98 | 99 | let failed = 0; 100 | 101 | test_re.forEach(re => { 102 | try { 103 | 104 | console.log('Testing', re); 105 | 106 | if (Test(re) != 'GOOD') { 107 | throw re; 108 | } 109 | 110 | } catch (e) { 111 | failed += 1; 112 | console.log('Failed', '' + e); 113 | } 114 | }); 115 | 116 | if (failed) { 117 | throw failed + ' errors'; 118 | } 119 | 120 | module.exports = Test; 121 | -------------------------------------------------------------------------------- /src/Expr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | */ 4 | 5 | import Z3 from "./Z3Loader"; 6 | 7 | class Expr { 8 | 9 | constructor(context, ast, checks) { 10 | this.context = context; 11 | 12 | this.ast = ast; 13 | this.context.incRef(this); 14 | 15 | this._fields = []; 16 | 17 | this.checks = checks || []; 18 | } 19 | 20 | /** 21 | * Singleton simplify params, just allocate once per execution and 22 | * leave it rather than inc_ref and dec_refing each time 23 | */ 24 | _simplifyParams() { 25 | if (!Expr._simpleParams) { 26 | let config = Z3.Z3_mk_params(this.context.ctx); 27 | Z3.Z3_params_inc_ref(this.context.ctx, config); 28 | Z3.Z3_params_set_bool(this.context.ctx, config, Z3.Z3_mk_string_symbol(this.context.ctx, "rewriter.elim_to_real"), true); 29 | Expr._simpleParams = config; 30 | } 31 | return Expr._simpleParams; 32 | } 33 | 34 | destroy() { 35 | this.context.decRef(this); 36 | this.ast = null; 37 | } 38 | 39 | toString() { 40 | return this.context.mkToString(this); 41 | } 42 | 43 | _sortName() { 44 | return this.context.mkSymbolString(this.context.mkSortName(this.context.mkGetSort(this))); 45 | } 46 | 47 | isString() { 48 | return this._sortName() == "String"; 49 | } 50 | 51 | toPrettyString() { 52 | let output = this.context.mkToString(this); 53 | output = output.replace(/\(not (\S)\)/g, "¬$1"); 54 | output = output.replace("or", "∨"); 55 | return output; 56 | } 57 | 58 | getBoolValue() { 59 | return Z3.Z3_get_bool_value(this.context.ctx, this.ast) == Z3.TRUE; 60 | } 61 | 62 | getRealValue() { 63 | let num_dec_string = Z3.Z3_get_numeral_decimal_string(this.context.ctx, this.ast, 30); 64 | return Number(num_dec_string); 65 | } 66 | 67 | escapeString(str) { 68 | function replacer(match, p1) { 69 | var chars = str[p1 + 2] + str[p1 + 3]; 70 | return String.fromCharCode(parseInt(chars, 16)); 71 | } 72 | 73 | function unicodeReplacer(match, p1) { 74 | var chars = str[p1 + 2] + str[p1 + 3] + str[p1 + 4] + str[p1 + 5]; 75 | return String.fromCharCode(parseInt(chars)); 76 | } 77 | 78 | return str.replace(/\\x[0-9a-fA-F]{2}/g, replacer).replace(/\\u\d{4}/g, unicodeReplacer).replace(/\\a/g, "\a").replace(/\\b/g, "\b").replace(/\\r/g, "\r").replace(/\\v/g, "\v").replace(/\\f/g, "\f").replace(/\\n/g, "\n").replace(/\\t/g, "\t"); 79 | } 80 | 81 | getLength() { 82 | if (this.isString()) { 83 | return this.context.mkSeqLength(this); 84 | } else { 85 | return this._length; 86 | } 87 | } 88 | 89 | getField(indexSymbol) { 90 | const sort = Z3.Z3_get_sort(this.context.ctx, this.ast); 91 | const is_string = Z3.Z3_is_string_sort(this.context.ctx, sort); 92 | return is_string ? this.context.mkSeqAt(this, indexSymbol) : this.context.mkSelect(this, indexSymbol); 93 | } 94 | 95 | setField(indexSymbol, valueSymbol) { 96 | return this.context.mkStore(this, indexSymbol, valueSymbol); 97 | } 98 | 99 | setLength(l) { 100 | this._length = l; 101 | return this; 102 | } 103 | 104 | getFields() { 105 | return this._fields; 106 | } 107 | 108 | setFields(f) { 109 | this._fields = f; 110 | return this; 111 | } 112 | 113 | addField(expr) { 114 | this._fields.push(expr); 115 | return this; 116 | } 117 | 118 | asConstant(mdl) { 119 | const sort = this._sortName(); 120 | 121 | if (sort === "Real" || sort === "Int") { 122 | return this.getRealValue(); 123 | } else if (sort === "Bool") { 124 | return this.getBoolValue(); 125 | } else if (sort === "String") { 126 | return this.escapeString(Z3.Z3_get_string(this.context.ctx, this.ast)); 127 | } else if (this.getLength()) { //Array 128 | 129 | //TODO: Propogating array lengths like this is horrible, find a better way 130 | let len = mdl.eval(this.getLength()).asConstant(mdl); 131 | 132 | let built = []; 133 | 134 | for (let i = 0; i < len; i++) { 135 | built.push(mdl.eval(this.context.mkSelect(this, this.context.mkIntVal(i))).asConstant(mdl)); 136 | } 137 | 138 | return built; 139 | } else { 140 | let obj = {}; 141 | 142 | this._fields.forEach(field => { 143 | obj[mdl.eval(field).asConstant(mdl)] = mdl.eval(this.context.mkSelect(this, field)).asConstant(mdl); 144 | }); 145 | 146 | return obj; 147 | } 148 | } 149 | 150 | simplify() { 151 | let newAst = Z3.Z3_simplify_ex(this.context.ctx, this.ast, this._simplifyParams()); 152 | Z3.Z3_inc_ref(this.context.ctx, newAst); 153 | this.destroy(); 154 | this.ast = newAst; 155 | return this; 156 | } 157 | } 158 | 159 | export default Expr; 160 | -------------------------------------------------------------------------------- /templates/post_ref_bindings.js: -------------------------------------------------------------------------------- 1 | var ffi = require('@makeomatic/ffi-napi'); 2 | var Z3 = ffi.Library(libPath, GeneratedBindings); 3 | 4 | /** 5 | * For some reason FFI doesn't work if a pointer passes to a renderer and then back to the master in Electron 6 | * To work around this we maintain an internal heap and rewrite all incoming pointers 7 | * TODO: Work out a way to clear free'd memory from the heap? 8 | */ 9 | 10 | var POINTERS = {}; 11 | var last_pointer_id = 0; 12 | 13 | function wrapPtr(ptr) { 14 | if (ptr && typeof(ptr) == "object") { 15 | POINTERS[last_pointer_id] = ptr; 16 | return { id: last_pointer_id++, _ptr: true }; 17 | } else { 18 | return ptr; 19 | } 20 | } 21 | 22 | function unwrap(ptr) { 23 | if (ptr && ptr._ptr) { 24 | return POINTERS[ptr.id]; 25 | } else if (ptr instanceof Array) { 26 | for (var i = 0; i < ptr.length; i++) { 27 | ptr[i] = unwrap(ptr[i]); 28 | } 29 | return ptr; 30 | } else { 31 | return ptr; 32 | } 33 | } 34 | 35 | /** 36 | * END OF UGLY HEAP 37 | */ 38 | 39 | for (var modifiedBinding in GeneratedBindings) { 40 | const originFn = Z3[modifiedBinding]; 41 | Z3[modifiedBinding] = function() { 42 | for (var i = 0; i < arguments.length; i++) { 43 | arguments[i] = unwrap(arguments[i]); 44 | } 45 | return wrapPtr(originFn.apply(this, arguments)); 46 | }; 47 | } 48 | 49 | Z3.bindings_model_eval = function(ctx, mdl, expr) { 50 | var pAST = ref.alloc(Z3.Ast, null); 51 | var result = Z3.Z3_model_eval(ctx, mdl, expr, true, pAST); 52 | return result != 0 ? pAST.deref() : null; 53 | } 54 | 55 | //////// End Z3 function definitions 56 | // Constants - these are taken from z3onsts.py (and reformatted for export) 57 | // enum Z3_lbool 58 | Z3.TRUE = 1; 59 | Z3.UNDEF = 0; 60 | Z3.FALSE = -1; 61 | 62 | // enum Z3_symbol_kind 63 | Z3.INT_SYMBOL = 0; 64 | Z3.STRING_SYMBOL = 1; 65 | 66 | // enum Z3_parameter_kind 67 | Z3.PARAMETER_FUNC_DECL = 6; 68 | Z3.PARAMETER_DOUBLE = 1; 69 | Z3.PARAMETER_SYMBOL = 3; 70 | Z3.PARAMETER_INT = 0; 71 | Z3.PARAMETER_AST = 5; 72 | Z3.PARAMETER_SORT = 4; 73 | Z3.PARAMETER_RATIONAL = 2; 74 | 75 | // enum Z3_sort_kind 76 | Z3.BV_SORT = 4; 77 | Z3.FINITE_DOMAIN_SORT = 8; 78 | Z3.ARRAY_SORT = 5; 79 | Z3.UNKNOWN_SORT = 1000; 80 | Z3.RELATION_SORT = 7; 81 | Z3.REAL_SORT = 3; 82 | Z3.INT_SORT = 2; 83 | Z3.UNINTERPRETED_SORT = 0; 84 | Z3.BOOL_SORT = 1; 85 | Z3.DATATYPE_SORT = 6; 86 | Z3.SEQ_SORT = 11; 87 | 88 | // enum Z3_ast_kind 89 | Z3.CONST_STR = 0; 90 | Z3.CONST_BOOL = 1; 91 | Z3.FUNC = 2; 92 | Z3.NUMERAL = 3; 93 | Z3.VARIABLE = 4; 94 | Z3.STR_VARIABLE = 5; 95 | Z3.INT_VARIABLE = 6; 96 | Z3.QUANTIFIER = 7; 97 | Z3.UNKNOWN_TYPE = 8; 98 | Z3.STAR_TYPE = 9; 99 | Z3.CONCAT_TYPE = 10; 100 | Z3.SEARCH_TYPE = 11; 101 | Z3.REPLACE_ALL = 12; 102 | Z3.SUBSTRING = 13; 103 | 104 | // enum Z3_ast_kind 105 | Z3.VAR_AST = 2; 106 | Z3.SORT_AST = 4; 107 | Z3.QUANTIFIER_AST = 3; 108 | Z3.UNKNOWN_AST = 1000; 109 | Z3.FUNC_DECL_AST = 5; 110 | Z3.NUMERAL_AST = 0; 111 | Z3.APP_AST = 1; 112 | 113 | // enum Z3_decl_kind 114 | Z3.OP_LABEL = 1792; 115 | Z3.OP_PR_REWRITE = 1294; 116 | Z3.OP_UNINTERPRETED = 2051; 117 | Z3.OP_SUB = 519; 118 | Z3.OP_ZERO_EXT = 1058; 119 | Z3.OP_ADD = 518; 120 | Z3.OP_IS_INT = 528; 121 | Z3.OP_BREDOR = 1061; 122 | Z3.OP_BNOT = 1051; 123 | Z3.OP_BNOR = 1054; 124 | Z3.OP_PR_CNF_STAR = 1315; 125 | Z3.OP_RA_JOIN = 1539; 126 | Z3.OP_LE = 514; 127 | Z3.OP_SET_UNION = 773; 128 | Z3.OP_PR_UNDEF = 1280; 129 | Z3.OP_BREDAND = 1062; 130 | Z3.OP_LT = 516; 131 | Z3.OP_RA_UNION = 1540; 132 | Z3.OP_BADD = 1028; 133 | Z3.OP_BUREM0 = 1039; 134 | Z3.OP_OEQ = 267; 135 | Z3.OP_PR_MODUS_PONENS = 1284; 136 | Z3.OP_RA_CLONE = 1548; 137 | Z3.OP_REPEAT = 1060; 138 | Z3.OP_RA_NEGATION_FILTER = 1544; 139 | Z3.OP_BSMOD0 = 1040; 140 | Z3.OP_BLSHR = 1065; 141 | Z3.OP_BASHR = 1066; 142 | Z3.OP_PR_UNIT_RESOLUTION = 1304; 143 | Z3.OP_ROTATE_RIGHT = 1068; 144 | Z3.OP_ARRAY_DEFAULT = 772; 145 | Z3.OP_PR_PULL_QUANT = 1296; 146 | Z3.OP_PR_APPLY_DEF = 1310; 147 | Z3.OP_PR_REWRITE_STAR = 1295; 148 | Z3.OP_IDIV = 523; 149 | Z3.OP_PR_GOAL = 1283; 150 | Z3.OP_PR_IFF_TRUE = 1305; 151 | Z3.OP_LABEL_LIT = 1793; 152 | Z3.OP_BOR = 1050; 153 | Z3.OP_PR_SYMMETRY = 1286; 154 | Z3.OP_TRUE = 256; 155 | Z3.OP_SET_COMPLEMENT = 776; 156 | Z3.OP_CONCAT = 1056; 157 | Z3.OP_PR_NOT_OR_ELIM = 1293; 158 | Z3.OP_IFF = 263; 159 | Z3.OP_BSHL = 1064; 160 | Z3.OP_PR_TRANSITIVITY = 1287; 161 | Z3.OP_SGT = 1048; 162 | Z3.OP_RA_WIDEN = 1541; 163 | Z3.OP_PR_DEF_INTRO = 1309; 164 | Z3.OP_NOT = 265; 165 | Z3.OP_PR_QUANT_INTRO = 1290; 166 | Z3.OP_UGT = 1047; 167 | Z3.OP_DT_RECOGNISER = 2049; 168 | Z3.OP_SET_INTERSECT = 774; 169 | Z3.OP_BSREM = 1033; 170 | Z3.OP_RA_STORE = 1536; 171 | Z3.OP_SLT = 1046; 172 | Z3.OP_ROTATE_LEFT = 1067; 173 | Z3.OP_PR_NNF_NEG = 1313; 174 | Z3.OP_PR_REFLEXIVITY = 1285; 175 | Z3.OP_ULEQ = 1041; 176 | Z3.OP_BIT1 = 1025; 177 | Z3.OP_BIT0 = 1026; 178 | Z3.OP_EQ = 258; 179 | Z3.OP_BMUL = 1030; 180 | Z3.OP_ARRAY_MAP = 771; 181 | Z3.OP_STORE = 768; 182 | Z3.OP_PR_HYPOTHESIS = 1302; 183 | Z3.OP_RA_RENAME = 1545; 184 | Z3.OP_AND = 261; 185 | Z3.OP_TO_REAL = 526; 186 | Z3.OP_PR_NNF_POS = 1312; 187 | Z3.OP_PR_AND_ELIM = 1292; 188 | Z3.OP_MOD = 525; 189 | Z3.OP_BUDIV0 = 1037; 190 | Z3.OP_PR_TRUE = 1281; 191 | Z3.OP_BNAND = 1053; 192 | Z3.OP_PR_ELIM_UNUSED_VARS = 1299; 193 | Z3.OP_RA_FILTER = 1543; 194 | Z3.OP_FD_LT = 1549; 195 | Z3.OP_RA_EMPTY = 1537; 196 | Z3.OP_DIV = 522; 197 | Z3.OP_ANUM = 512; 198 | Z3.OP_MUL = 521; 199 | Z3.OP_UGEQ = 1043; 200 | Z3.OP_BSREM0 = 1038; 201 | Z3.OP_PR_TH_LEMMA = 1318; 202 | Z3.OP_BXOR = 1052; 203 | Z3.OP_DISTINCT = 259; 204 | Z3.OP_PR_IFF_FALSE = 1306; 205 | Z3.OP_BV2INT = 1072; 206 | Z3.OP_EXT_ROTATE_LEFT = 1069; 207 | Z3.OP_PR_PULL_QUANT_STAR = 1297; 208 | Z3.OP_BSUB = 1029; 209 | Z3.OP_PR_ASSERTED = 1282; 210 | Z3.OP_BXNOR = 1055; 211 | Z3.OP_EXTRACT = 1059; 212 | Z3.OP_PR_DER = 1300; 213 | Z3.OP_DT_CONSTRUCTOR = 2048; 214 | Z3.OP_GT = 517; 215 | Z3.OP_BUREM = 1034; 216 | Z3.OP_IMPLIES = 266; 217 | Z3.OP_SLEQ = 1042; 218 | Z3.OP_GE = 515; 219 | Z3.OP_BAND = 1049; 220 | Z3.OP_ITE = 260; 221 | Z3.OP_AS_ARRAY = 778; 222 | Z3.OP_RA_SELECT = 1547; 223 | Z3.OP_CONST_ARRAY = 770; 224 | Z3.OP_BSDIV = 1031; 225 | Z3.OP_OR = 262; 226 | Z3.OP_PR_HYPER_RESOLVE = 1319; 227 | Z3.OP_AGNUM = 513; 228 | Z3.OP_PR_PUSH_QUANT = 1298; 229 | Z3.OP_BSMOD = 1035; 230 | Z3.OP_PR_IFF_OEQ = 1311; 231 | Z3.OP_INTERP = 268; 232 | Z3.OP_PR_LEMMA = 1303; 233 | Z3.OP_SET_SUBSET = 777; 234 | Z3.OP_SELECT = 769; 235 | Z3.OP_RA_PROJECT = 1542; 236 | Z3.OP_BNEG = 1027; 237 | Z3.OP_UMINUS = 520; 238 | Z3.OP_REM = 524; 239 | Z3.OP_TO_INT = 527; 240 | Z3.OP_PR_QUANT_INST = 1301; 241 | Z3.OP_SGEQ = 1044; 242 | Z3.OP_POWER = 529; 243 | Z3.OP_XOR3 = 1074; 244 | Z3.OP_RA_IS_EMPTY = 1538; 245 | Z3.OP_CARRY = 1073; 246 | Z3.OP_DT_ACCESSOR = 2050; 247 | Z3.OP_PR_TRANSITIVITY_STAR = 1288; 248 | Z3.OP_PR_NNF_STAR = 1314; 249 | Z3.OP_PR_COMMUTATIVITY = 1307; 250 | Z3.OP_ULT = 1045; 251 | Z3.OP_BSDIV0 = 1036; 252 | Z3.OP_SET_DIFFERENCE = 775; 253 | Z3.OP_INT2BV = 1071; 254 | Z3.OP_XOR = 264; 255 | Z3.OP_PR_MODUS_PONENS_OEQ = 1317; 256 | Z3.OP_BNUM = 1024; 257 | Z3.OP_BUDIV = 1032; 258 | Z3.OP_PR_MONOTONICITY = 1289; 259 | Z3.OP_PR_DEF_AXIOM = 1308; 260 | Z3.OP_FALSE = 257; 261 | Z3.OP_EXT_ROTATE_RIGHT = 1070; 262 | Z3.OP_PR_DISTRIBUTIVITY = 1291; 263 | Z3.OP_SIGN_EXT = 1057; 264 | Z3.OP_PR_SKOLEMIZE = 1316; 265 | Z3.OP_BCOMP = 1063; 266 | Z3.OP_RA_COMPLEMENT = 1546; 267 | 268 | // enum Z3_param_kind 269 | Z3.PK_BOOL = 1; 270 | Z3.PK_SYMBOL = 3; 271 | Z3.PK_OTHER = 5; 272 | Z3.PK_INVALID = 6; 273 | Z3.PK_UINT = 0; 274 | Z3.PK_STRING = 4; 275 | Z3.PK_DOUBLE = 2; 276 | 277 | // enum Z3_search_failure 278 | Z3.QUANTIFIERS = 7; 279 | Z3.UNKNOWN = 1; 280 | Z3.CANCELED = 4; 281 | Z3.MEMOUT_WATERMARK = 3; 282 | Z3.THEORY = 6; 283 | Z3.NO_FAILURE = 0; 284 | Z3.TIMEOUT = 2; 285 | Z3.NUM_CONFLICTS = 5; 286 | 287 | // enum Z3_ast_print_mode 288 | Z3.PRINT_SMTLIB2_COMPLIANT = 3; 289 | Z3.PRINT_SMTLIB_COMPLIANT = 2; 290 | Z3.PRINT_SMTLIB_FULL = 0; 291 | Z3.PRINT_LOW_LEVEL = 1; 292 | 293 | // enum Z3_error_code 294 | Z3.INVALID_PATTERN = 6; 295 | Z3.MEMOUT_FAIL = 7; 296 | Z3.NO_PARSER = 5; 297 | Z3.OK = 0; 298 | Z3.INVALID_ARG = 3; 299 | Z3.EXCEPTION = 12; 300 | Z3.IOB = 2; 301 | Z3.INTERNAL_FATAL = 9; 302 | Z3.INVALID_USAGE = 10; 303 | Z3.FILE_ACCESS_ERROR = 8; 304 | Z3.SORT_ERROR = 1; 305 | Z3.PARSER_ERROR = 4; 306 | Z3.DEC_REF_ERROR = 11; 307 | 308 | // enum Z3_goal_prec; 309 | Z3.GOAL_UNDER = 1; 310 | Z3.GOAL_PRECISE = 0; 311 | Z3.GOAL_UNDER_OVER = 3; 312 | Z3.GOAL_OVER = 2; 313 | 314 | /////// end constants 315 | 316 | //// Exported types 317 | Z3.Ast = Ast; 318 | 319 | Z3.AstArray = AstArray; 320 | Z3.CUIntArray = CUIntArray; 321 | Z3.SymbolArray = SymbolArray 322 | Z3.SortArray = SortArray; 323 | Z3.FuncDeclArray = FuncDeclArray; 324 | Z3.ConstructorArray = ConstructorArray; 325 | Z3.ConstructorListArray = ConstructorListArray; 326 | Z3.PatternArray = PatternArray; 327 | Z3.TacticObjArray = TacticObjArray; 328 | Z3.RCFNumObjArray = RCFNumObjArray; 329 | 330 | return Z3; 331 | }; 332 | 333 | module.exports.default = module.exports; 334 | -------------------------------------------------------------------------------- /src/Context.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | */ 4 | 5 | import Z3 from "./Z3Loader"; 6 | import Z3Utils from "./Z3Utils"; 7 | import Expr from "./Expr"; 8 | 9 | class Context { 10 | 11 | constructor() { 12 | let config = Z3.Z3_mk_config(); 13 | 14 | Z3.Z3_set_param_value(config, "model", "true"); 15 | 16 | this.ctx = Z3.Z3_mk_context_rc(config); 17 | Z3.Z3_del_config(config); 18 | } 19 | 20 | _nullExpr() { 21 | return new Expr(this, null); 22 | } 23 | 24 | _appendList(list, l2) { 25 | return l2 ? list.concat(l2) : list; 26 | } 27 | 28 | /** 29 | * TODO: is not recursive on array 30 | */ 31 | _buildChecks(args) { 32 | return args.filter(next => next.checks).reduce((last, next) => this._appendList(last, next.checks), []); 33 | } 34 | 35 | _build(func, ...args) { 36 | return this._buildConst.apply(this, [func, this._buildChecks(args, false)].concat(Z3Utils.astArray(args))); 37 | } 38 | 39 | _buildConst(func, checks, ...args) { 40 | let fnResult = func.apply(this, [this.ctx].concat(args)); 41 | return new Expr(this, fnResult, checks); 42 | } 43 | 44 | _buildVar(func, ...args) { 45 | return this._buildVarNoArgs(func, args); 46 | } 47 | 48 | _buildVarNoArgs(func, args) { 49 | return new Expr(this, func(this.ctx, args.length, Z3Utils.astArray(args)), this._buildChecks(args, false)); 50 | } 51 | 52 | destroy() { 53 | return this._build(Z3.Z3_del_context); 54 | } 55 | 56 | incRef(e) { 57 | Z3.Z3_inc_ref(this.ctx, e.ast); 58 | } 59 | 60 | decRef(e) { 61 | Z3.Z3_dec_ref(this.ctx, e.ast); 62 | } 63 | 64 | mkApp(func, args) { 65 | return this._build(Z3.Z3_mk_app, func, args.length, args); 66 | } 67 | 68 | mkArray(name, baseSort) { 69 | let arraySort = this.mkArraySort(this.mkIntSort(), baseSort); 70 | let arrayInstance = this.mkVar(name, arraySort); 71 | let arrayLen = this.mkIntVar(name + "_Array_Length"); 72 | return arrayInstance.setLength(arrayLen); 73 | } 74 | 75 | mkObject(name, baseSort) { 76 | let objectSort = this.mkArraySort(this.mkStringSort(), baseSort); 77 | let objectInstance = this.mkVar(name, objectSort); 78 | return objectInstance; 79 | } 80 | 81 | mkGetSort(e) { 82 | return Z3.Z3_get_sort(this.ctx, e.ast); 83 | } 84 | 85 | mkSortName(sort) { 86 | return Z3.Z3_get_sort_name(this.ctx, sort); 87 | } 88 | 89 | mkSymbolString(s) { 90 | return Z3.Z3_get_symbol_string(this.ctx, s); 91 | } 92 | 93 | mkVar(name, sort) { 94 | return this._build(Z3.Z3_mk_const, this.mkStringSymbol(name), sort); 95 | } 96 | 97 | mkIntVar(name) { 98 | return this.mkVar(name, this.mkIntSort()); 99 | } 100 | 101 | mkRealVar(name) { 102 | return this.mkVar(name, this.mkRealSort()); 103 | } 104 | 105 | mkBoolVar(name) { 106 | return this.mkVar(name, this.mkBoolSort()); 107 | } 108 | 109 | mkString(val) { 110 | return this._buildConst(Z3.Z3_mk_string, [], val); 111 | } 112 | 113 | mkStringVar(name) { 114 | return this.mkVar(name, this.mkStringSort()); 115 | } 116 | 117 | mkIntVal(val) { 118 | return this.mkInt(val, this.mkIntSort()); 119 | } 120 | 121 | mkUnsignedIntVal(val) { 122 | return this.mkUnsignedInt(val, this.mkIntSort()); 123 | } 124 | 125 | mkSeqLength(val) { 126 | return this._build(Z3.Z3_mk_seq_length, val); 127 | } 128 | 129 | mkSeqAt(val, off) { 130 | return this._build(Z3.Z3_mk_seq_at, val, off); 131 | } 132 | 133 | mkSeqContains(val1, val2) { 134 | return this._build(Z3.Z3_mk_seq_contains, val1, val2); 135 | } 136 | 137 | mkSeqConcat(strings) { 138 | return this._buildVarNoArgs(Z3.Z3_mk_seq_concat, strings); 139 | } 140 | 141 | mkSeqSubstr(str, offset, length) { 142 | 143 | if (!length) { 144 | length = this._nullExpr(); 145 | } 146 | 147 | return this._build(Z3.Z3_mk_seq_extract, str, offset, length); 148 | } 149 | 150 | mkSeqIndexOf(str, str2, off) { 151 | return this._build(Z3.Z3_mk_seq_index, str, str2, off); 152 | } 153 | 154 | mkStrToInt(str) { 155 | return this._build(Z3.Z3_mk_str_to_int, str); 156 | } 157 | 158 | mkIntToStr(num) { 159 | return this._build(Z3.Z3_mk_int_to_str, num); 160 | } 161 | 162 | mkSeqInRe(seq, re) { 163 | return this._build(Z3.Z3_mk_seq_in_re, seq, re); 164 | } 165 | 166 | mkReConcat(re1, re2) { 167 | return this._buildVar(Z3.Z3_mk_re_concat, re1, re2); 168 | } 169 | 170 | mkReEmpty() { 171 | return this._build(Z3.Z3_mk_re_empty, this.mkReSort(this.mkStringSort())); 172 | } 173 | 174 | mkReFull() { 175 | return this._build(Z3.Z3_mk_re_full, this.mkStringSort()); 176 | } 177 | 178 | mkReOption(re) { 179 | return this._build(Z3.Z3_mk_re_option, re); 180 | } 181 | 182 | mkReStar(re) { 183 | return this._build(Z3.Z3_mk_re_star, re); 184 | } 185 | 186 | mkReUnion(re1, re2) { 187 | return this._buildVar(Z3.Z3_mk_re_union, re1, re2); 188 | } 189 | 190 | mkReIntersect(re1, re2) { 191 | return this._buildVar(Z3.Z3_mk_re_intersect, re1, re2); 192 | } 193 | 194 | mkReComplement(re) { 195 | return this._build(Z3.Z3_mk_re_complement, re); 196 | } 197 | 198 | mkRePlus(re) { 199 | return this._build(Z3.Z3_mk_re_plus, re); 200 | } 201 | 202 | mkReRange(ch1, ch2) { 203 | return this._build(Z3.Z3_mk_re_range, ch1, ch2); 204 | } 205 | 206 | mkReLoop(re, lo, hi) { 207 | return this._build(Z3.Z3_mk_re_loop, re, lo, hi); 208 | } 209 | 210 | mkSeqToRe(seq) { 211 | return this._build(Z3.Z3_mk_seq_to_re, seq); 212 | } 213 | 214 | isString(ast) { 215 | return this.build(Z3.Z3_is_string, ast) === Z3.TRUE; 216 | } 217 | 218 | mkBoolSort() { 219 | return Z3.Z3_mk_bool_sort(this.ctx); 220 | } 221 | 222 | mkStringSort() { 223 | return Z3.Z3_mk_string_sort(this.ctx); 224 | } 225 | 226 | mkIntSort() { 227 | return Z3.Z3_mk_int_sort(this.ctx); 228 | } 229 | 230 | mkSeqSort(sort) { 231 | return Z3.Z3_mk_seq_sort(this.ctx, sort); 232 | } 233 | 234 | mkReSort(sort) { 235 | return Z3.Z3_mk_re_sort(this.ctx, sort); 236 | } 237 | 238 | mkIntSymbol(val) { 239 | return Z3.Z3_mk_int_symbol(this.ctx, val); 240 | } 241 | 242 | mkStringSymbol(val) { 243 | return Z3.Z3_mk_string_symbol(this.ctx, val); 244 | } 245 | 246 | mkConst(symb, sort) { 247 | return this._build(Z3.Z3_mk_const, symb, sort); 248 | } 249 | 250 | mkFunc(name, args, sort) { 251 | return this._build(Z3.Z3_mk_func_decl, name, args.length, args, sort); 252 | } 253 | 254 | mkRecFunc(name, args, sort) { 255 | return this._build(Z3.Z3_mk_rec_func_decl, name, args.length, args, sort); 256 | } 257 | 258 | mkRecFuncDef(fn, args, body) { 259 | return this._build(Z3.Z3_add_rec_func_decl, fn, args.length, args, body); 260 | } 261 | 262 | /** 263 | * Propositional logic and equality 264 | */ 265 | 266 | mkTrue() { 267 | return this._build(Z3.Z3_mk_true); 268 | } 269 | 270 | mkFalse() { 271 | return this._build(Z3.Z3_mk_false); 272 | } 273 | 274 | mkEq(left, right) { 275 | return this._build(Z3.Z3_mk_eq, left, right); 276 | } 277 | 278 | //missing: distinct 279 | 280 | mkNot(arg) { 281 | return this._build(Z3.Z3_mk_not, arg); 282 | } 283 | 284 | mkIte(ifarg, thenarg, elsearg) { 285 | return this._build(Z3.Z3_mk_ite, ifarg, thenarg, elsearg); 286 | } 287 | 288 | mkIff(left, right) { 289 | return this._build(Z3.Z3_mk_iff, left, right); 290 | } 291 | 292 | mkImplies(left, right) { 293 | return this._build(Z3.Z3_mk_implies, left, right); 294 | } 295 | 296 | mkXOr(left, right) { 297 | return this._build(Z3.Z3_mk_xor, left, right); 298 | } 299 | 300 | mkAnd(left, right) { 301 | return this._buildVar(Z3.Z3_mk_and, left, right); 302 | } 303 | 304 | mkAndList(conditions) { 305 | return this._buildVarNoArgs(Z3.Z3_mk_and, conditions); 306 | } 307 | 308 | mkOr(left, right) { 309 | return this._buildVar(Z3.Z3_mk_or, left, right); 310 | } 311 | 312 | /** 313 | * Arithmetic: Integers and Reals 314 | */ 315 | 316 | mkRealSort() { 317 | return Z3.Z3_mk_real_sort(this.ctx); 318 | } 319 | 320 | mkDoubleSort() { 321 | return Z3.Z3_mk_fpa_sort_64(this.ctx); 322 | } 323 | 324 | mkAdd(left, right) { 325 | return this._buildVar(Z3.Z3_mk_add, left, right); 326 | } 327 | 328 | mkMul(left, right) { 329 | return this._buildVar(Z3.Z3_mk_mul, left, right); 330 | } 331 | 332 | mkSub(left, right) { 333 | return this._buildVar(Z3.Z3_mk_sub, left, right); 334 | } 335 | 336 | mkUnaryMinus(arg) { 337 | return this._build(Z3.Z3_mk_unary_minus, arg); 338 | } 339 | 340 | mkDiv(arg1, arg2) { 341 | return this._build(Z3.Z3_mk_div, arg1, arg2); 342 | } 343 | 344 | mkBitwiseShiftLeft(arg1, arg2) { 345 | return this._build(Z3.Z3_mk_bvshl, arg1, arg2); 346 | } 347 | 348 | mkBitwiseShiftRight(arg1, arg2) { 349 | return this._build(Z3.Z3_mk_bvlshr, arg1, arg2); 350 | } 351 | 352 | mkMod(arg1, arg2) { 353 | return this._build(Z3.Z3_mk_mod, arg1, arg2); 354 | } 355 | 356 | mkRem(arg1, arg2) { 357 | return this._build(Z3.Z3_mk_rem, arg1, arg2); 358 | } 359 | 360 | mkPower(arg1, arg2) { 361 | return this._build(Z3.Z3_mk_power, arg1, arg2); 362 | } 363 | 364 | mkLt(left, right) { 365 | return this._build(Z3.Z3_mk_lt, left, right); 366 | } 367 | 368 | mkLe(left, right) { 369 | return this._build(Z3.Z3_mk_le, left, right); 370 | } 371 | 372 | mkGt(left, right) { 373 | return this._build(Z3.Z3_mk_gt, left, right); 374 | } 375 | 376 | mkGe(left, right) { 377 | return this._build(Z3.Z3_mk_ge, left, right); 378 | } 379 | 380 | mkRealToInt(real) { 381 | return this._build(Z3.Z3_mk_real2int, real); 382 | } 383 | 384 | mkIntToReal(ival) { 385 | return this._build(Z3.Z3_mk_int2real, ival); 386 | } 387 | 388 | mkIntToBv(ival) { 389 | return this._build(Z3.Z3_mk_int2bv, 32, ival); 390 | } 391 | 392 | mkBvToInt(bval) { 393 | return this._build(Z3.Z3_mk_bv2int, bval, true); 394 | } 395 | 396 | mkIsInt(arg) { 397 | return this._build(Z3.Z3_mk_is_int, arg); 398 | } 399 | 400 | /** 401 | * Numerals 402 | */ 403 | 404 | mkNumeral(numeral, sort) { 405 | return this._build(Z3.Z3_mk_numeral, numeral, sort); 406 | } 407 | 408 | mkReal(num, den) { 409 | return this._build(Z3.Z3_mk_real, num, den); 410 | } 411 | 412 | mkInt(v, sort) { 413 | return this._build(Z3.Z3_mk_int, v, sort); 414 | } 415 | 416 | mkUnsignedInt(v, sort) { 417 | return this._build(Z3.Z3_mk_unsigned_int, v, sort); 418 | } 419 | 420 | mkInt64(v, sort) { 421 | return this._build(Z3.Z3_mk_int64, v, sort); 422 | } 423 | 424 | mkUnsignedInt64(v, sort) { 425 | return this._build(Z3.Z3_mk_unsigned_int64, v, sort); 426 | } 427 | 428 | mkToString(e) { 429 | return Z3.Z3_ast_to_string(this.ctx, e.ast || e); 430 | } 431 | 432 | /** 433 | * Arrays 434 | */ 435 | 436 | mkArraySort(indexSort, elemSort) { 437 | return this._build(Z3.Z3_mk_array_sort, indexSort, elemSort); 438 | } 439 | 440 | mkSelect(array, index) { 441 | return this._build(Z3.Z3_mk_select, array, index); 442 | } 443 | 444 | mkStore(array, index, v) { 445 | return this._build(Z3.Z3_mk_store, array, index, v) 446 | .setLength(array.getLength()); 447 | } 448 | 449 | mkConstArray(sort, v) { 450 | return this._build(Z3.Z3_mk_const_array, sort, v) 451 | .setLength(this.mkIntVal(v.length)); 452 | } 453 | 454 | /** 455 | * Quantifiers 456 | * SEE: https://stackoverflow.com/questions/9777381/c-api-for-quantifiers 457 | */ 458 | 459 | /// https://z3prover.github.io/api/html/group__capi.html#gaa80db40fee2eb0124922726e1db97b43 460 | mkBound(index, sort) { 461 | return this._build(Z3.Z3_mk_bound, index, sort); 462 | } 463 | 464 | /// Weight, and patterns are optional. Bound should be an array of consts. 465 | mkForAllConst(bound, body, patterns = [], weight = 0) { 466 | return this._build(Z3.mkForAllConst, weight, bound.length, bound, patterns.length, patterns, body); 467 | } 468 | 469 | mkForAll(decl_names, sorts, body, patterns = [], weight = 0) { 470 | return this._build(Z3.Z3_mk_forall, weight, patterns.length, patterns, decl_names.length, [sorts], decl_names, body); 471 | } 472 | 473 | mkExists(decl_names, sorts, body, patterns = [], weight = 0) { 474 | return this._build(Z3.Z3_mk_exists, weight, patterns.length, patterns, decl_names.length, [sorts], decl_names, body); 475 | } 476 | 477 | /// Weight, and patterns are optional. Bound should be an array of consts. 478 | mkExistsConst(bound, body, patterns = [], weight = 0) { 479 | return this._build(Z3.Z3_mk_exists_const, weight, bound.length, bound, patterns.length, patterns, body); 480 | } 481 | 482 | mkPattern(terms) { 483 | return this._build(Z3.Z3_mk_pattern, terms.length, terms); 484 | } 485 | } 486 | 487 | export default Context; 488 | -------------------------------------------------------------------------------- /src/Regex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Blake Loring 2015 3 | * Approximate JavaScript regular expression to Z3 regex parser 4 | */ 5 | 6 | function CullOuterRegex(regex) { 7 | let firstSlash = regex.indexOf("/"); 8 | let lastSlash = regex.lastIndexOf("/"); 9 | return regex.substr(firstSlash + 1, lastSlash - 1); 10 | } 11 | 12 | function FindClosingParen(regex, idx) { 13 | let open = 1; 14 | 15 | while (idx < regex.length) { 16 | 17 | if (regex[idx - 1] != "\\" && regex[idx] == "(") { open++; } 18 | if (regex[idx - 1] != "\\" && regex[idx] == ")") { open--; } 19 | 20 | if (open == 0) { 21 | return idx; 22 | } 23 | 24 | idx++; 25 | } 26 | 27 | return -1; 28 | } 29 | 30 | function Desugar(regex) { 31 | 32 | let i; 33 | 34 | //Strip ?! 35 | while ((i = regex.indexOf("(?!")) != -1 || (i = regex.indexOf("?=")) != -1) { 36 | let end = FindClosingParen(regex, i + 1); 37 | regex = regex.slice(0, i) + regex.slice(end + 1); 38 | } 39 | 40 | while ((i = regex.indexOf("(")) != -1 || (i = regex.indexOf(")")) != -1) { 41 | if (regex[i - 1] != "\\") { 42 | regex = regex.slice(0, i) + regex.slice(i + 1); 43 | } 44 | } 45 | 46 | //Remove word boundaries 47 | regex = regex.replace(/\\b|\\B/g, ""); 48 | 49 | return regex; 50 | } 51 | 52 | let REGEX_CTR = 0; 53 | 54 | function RegexRecursive(ctx, regex, idx) { 55 | 56 | const pp_steps = []; 57 | 58 | let lrctr = REGEX_CTR++; 59 | 60 | let captures = []; 61 | let previousCaptureAst = []; 62 | let assertions = []; 63 | let fill_ctr = 0; 64 | let backreferences = false; 65 | 66 | function BuildError(msg) { 67 | return { 68 | error: msg, 69 | idx: idx, 70 | remaining: regex.slice(idx) 71 | }; 72 | } 73 | 74 | //TODO: This is a bad way of handling symbolIn, in general the whole processing fillers is weak 75 | let shouldAddFillerIn = true; 76 | 77 | function nextFiller() { 78 | return ctx.mkStringVar("" + lrctr + " Fill " + fill_ctr++); 79 | } 80 | 81 | function moreRange() { 82 | return idx < regex.length; 83 | } 84 | 85 | function more() { 86 | return moreRange() && current() != "|" && current() != ")"; 87 | } 88 | 89 | function mk(v) { 90 | return ctx.mkSeqToRe(ctx.mkString(v)); 91 | } 92 | 93 | function current() { 94 | 95 | if (regex.length > idx) { 96 | return regex[idx]; 97 | } 98 | 99 | return undefined; 100 | } 101 | 102 | function next(num) { 103 | let r = current(); 104 | idx += (num || 1); 105 | return r; 106 | } 107 | 108 | function peek(num) { 109 | 110 | if (typeof(num) === "undefined") { 111 | num = 1; 112 | } 113 | 114 | return regex[idx + num]; 115 | } 116 | 117 | function Any() { 118 | let beforeNewline = ctx.mkReRange(ctx.mkString("\\x00"), ctx.mkString("\\x09")); 119 | let afterNewline = ctx.mkReRange(ctx.mkString("\\x0b"), ctx.mkString("\\xff")); 120 | return ctx.mkReUnion(beforeNewline, afterNewline); 121 | } 122 | 123 | /** 124 | * The . character isnt all chracters 125 | * This will accept any character 126 | */ 127 | function TruelyAny() { 128 | return ctx.mkReRange(ctx.mkString("\\x00"), ctx.mkString("\\xff")); 129 | } 130 | 131 | function ParseRangerNextEscaped() { 132 | let c1 = next(); 133 | if (c1 == "\\") { 134 | return next(); 135 | } else { 136 | return c1; 137 | } 138 | } 139 | 140 | function ParseRangeInner() { 141 | 142 | let union = undefined; 143 | 144 | while (moreRange() && current() != "]") { 145 | 146 | let c1 = ParseRangerNextEscaped(); 147 | let range = undefined; 148 | 149 | if (current() == "-" && peek() != "]") { 150 | next(); 151 | let c2 = ParseRangerNextEscaped(); 152 | range = ctx.mkReRange(ctx.mkString(c1), ctx.mkString(c2)); 153 | } else { 154 | range = ctx.mkSeqToRe(ctx.mkString(c1)); 155 | } 156 | 157 | if (!union) { 158 | union = range; 159 | } else { 160 | union = ctx.mkReUnion(union, range); 161 | } 162 | } 163 | 164 | return union; 165 | } 166 | 167 | function ParseRange() { 168 | next(); 169 | 170 | let negate = false; 171 | 172 | if (current() == "^") { 173 | next(); 174 | negate = true; 175 | } 176 | 177 | let r = ParseRangeInner(); 178 | 179 | if (negate) { 180 | let comp = ctx.mkReComplement(r); 181 | r = ctx.mkReIntersect(TruelyAny(), comp); 182 | } 183 | 184 | if (next() == "]") { 185 | return r; 186 | } else { 187 | throw BuildError("Regex Parsing Error (Range)"); 188 | } 189 | } 190 | 191 | let Specials = { 192 | ".": Any 193 | }; 194 | 195 | function Alpha() { 196 | let p1 = ctx.mkReRange(ctx.mkString("a"), ctx.mkString("z")); 197 | let p2 = ctx.mkReRange(ctx.mkString("A"), ctx.mkString("Z")); 198 | return ctx.mkReUnion(p1, p2); 199 | } 200 | 201 | function Digit() { 202 | return ctx.mkReRange(ctx.mkString("0"), ctx.mkString("9")); 203 | } 204 | 205 | function Whitespace() { 206 | let p1 = mk(" "); 207 | let p2 = mk("\t"); 208 | let p3 = mk("\r"); 209 | let p4 = mk("\n"); 210 | let p5 = mk("\f"); 211 | let p6 = mk("\v"); 212 | return ctx.mkReUnion(p1, ctx.mkReUnion(p2, ctx.mkReUnion(p3, ctx.mkReUnion(p4, ctx.mkReUnion(p5, p6))))); 213 | } 214 | 215 | function AlphaNumeric() { 216 | return ctx.mkReUnion(Alpha(), Digit()); 217 | } 218 | 219 | function Word() { 220 | return ctx.mkReUnion(AlphaNumeric(), mk("_")); 221 | } 222 | 223 | function ParseAtom1() { 224 | let parsed_str = next(); 225 | 226 | const IS_JUST_TEXT = /^[a-zA-Z0-9]$/; 227 | const IS_SPECIAL = /^[*+?]$/; 228 | 229 | //Hack to greedly eat anything that is definately not a special character 230 | //Makes SMT formulee look prettier 231 | //We need to look ahead for this and just drop back to standard parsing atom by atom if 232 | //the lookahead is special 233 | while (current() && IS_JUST_TEXT.test(current()) && !IS_SPECIAL.test(peek())) { 234 | parsed_str += next(); 235 | } 236 | 237 | return mk(parsed_str); 238 | } 239 | 240 | function ParseMaybeSpecial() { 241 | if (Specials[current()]) { 242 | return Specials[next()](); 243 | } else { 244 | return ParseAtom1(); 245 | } 246 | } 247 | 248 | function isHex(char) { 249 | return /^[0-9A-Fa-f]+$/.test(char); 250 | } 251 | 252 | /* 253 | function isDigits(char) { 254 | return /^[0-9]+$/.test(char); 255 | } 256 | */ 257 | 258 | function ParseMaybeEscaped(captureIndex) { 259 | if (current() == "\\") { 260 | next(); 261 | 262 | let c = next(); 263 | 264 | if (c == "d") { 265 | return Digit(); 266 | } else if (c == "D") { 267 | return ctx.mkReIntersect(TruelyAny(), ctx.mkReComplement(Digit())); 268 | } else if (c == "w") { 269 | return Word(); 270 | } else if (c == "W") { 271 | return ctx.mkReIntersect(TruelyAny(), ctx.mkReComplement(Word())); 272 | } else if (c == "s") { 273 | return Whitespace(); 274 | } else if (c == "S") { 275 | return ctx.mkReIntersect(TruelyAny(), ctx.mkReComplement(Whitespace())); 276 | } else if (c == "n") { 277 | return mk("\n"); 278 | } else if (c == "x") { 279 | let c1 = next(); 280 | let c2 = next(); 281 | 282 | if (!isHex(c1) || !isHex(c2)) { 283 | throw BuildError("Expected hex character at " + c1 + " and " + c2); 284 | } 285 | 286 | let hexToInt = parseInt(c1 + c2, 16); 287 | let hexAsString = String.fromCharCode(hexToInt); 288 | 289 | return mk(hexAsString); 290 | } else if (c == "u") { 291 | 292 | let expectingRBrace = false; 293 | 294 | if (current() == "{") { 295 | expectingRBrace = true; 296 | next(); 297 | } 298 | 299 | let unicodeSequence = next() + next() + next() + next(); 300 | 301 | if (expectingRBrace && next() != "}") { 302 | throw BuildError("Expecting RBrace in unicode sequence"); 303 | } 304 | 305 | if (!isHex(unicodeSequence)) { 306 | throw BuildError("Expected digits in unicode sequence " + unicodeSequence); 307 | } 308 | 309 | let unicodeString = String.fromCharCode(parseInt(unicodeSequence, 16)); 310 | return mk(unicodeString); 311 | } else if (c == "r") { 312 | return mk("\r"); 313 | } else if (c == "v") { 314 | return mk("\v"); 315 | } else if (c == "t") { 316 | return mk("\t"); 317 | } else if (c == "f") { 318 | return mk("\f"); 319 | } else if (c >= "1" && c <= "9") { 320 | 321 | let idx = parseInt(c); 322 | 323 | if (idx < captures.length) { 324 | backreferences = true; 325 | addToCapture(captureIndex, captures[idx]); 326 | shouldAddFillerIn = false; 327 | return previousCaptureAst[idx]; 328 | } else { 329 | return mk(""); 330 | } 331 | 332 | } else if (c == "b") { 333 | pp_steps.push({ type: "b", idx: idx }); 334 | return mk(""); 335 | } else if (c == "B") { 336 | pp_steps.push({ type: "B", idx: idx }); 337 | return mk(""); 338 | } else if (c == "0") { 339 | return mk("\\x00"); 340 | } 341 | 342 | return mk(c); 343 | } else { 344 | return ParseMaybeSpecial(); 345 | } 346 | } 347 | 348 | function ParseMaybeRange(captureIndex) { 349 | if (current() == "[") { 350 | return ParseRange(); 351 | } else { 352 | return ParseMaybeEscaped(captureIndex); 353 | } 354 | } 355 | 356 | function rewriteCaptureOptional(idx) { 357 | //Rewrite capture[n] to be capture[n] or '' 358 | let orFiller = nextFiller(); 359 | either(orFiller, captures[idx], ctx.mkString("")); 360 | captures[idx] = orFiller; 361 | } 362 | 363 | 364 | function addToCapture(idx, thing) { 365 | captures[idx] = ctx.mkSeqConcat([captures[idx], thing]); 366 | } 367 | 368 | function symbolIn(atoms) { 369 | let nfil = nextFiller(); 370 | assertions.push(ctx.mkSeqInRe(nfil, atoms)); 371 | return nfil; 372 | } 373 | 374 | function ParseMaybeCaptureGroupStart(captureIndex) { 375 | function buildPlusConstraints(atoms, plusGroup) { 376 | let ncap = captures[plusGroup]; 377 | 378 | atoms = ctx.mkRePlus(atoms); 379 | 380 | //String = Something + Capture ^ in atoms 381 | let added = ctx.mkSeqConcat([nextFiller(), ncap]); 382 | assertions.push(ctx.mkSeqInRe(added, atoms)); 383 | 384 | addToCapture(captureIndex, added); 385 | return atoms; 386 | } 387 | 388 | function buildStarConstraints(atoms, starGroup) { 389 | let ncap = captures[starGroup]; 390 | 391 | atoms = ctx.mkReStar(atoms); 392 | 393 | let added = ctx.mkSeqConcat([nextFiller(), ncap]); 394 | assertions.push(ctx.mkImplies(ctx.mkEq(ncap, ctx.mkString("")), ctx.mkEq(added, ctx.mkString("")))); 395 | 396 | addToCapture(captureIndex, added); 397 | return atoms; 398 | } 399 | 400 | /* 401 | function buildOptionConstraints(atoms, optionGroup) { 402 | addToCapture(captureIndex, captures[optionGroup]); 403 | } 404 | */ 405 | 406 | if (current() == "(") { 407 | next(); 408 | 409 | let capture = true; 410 | 411 | //Ignore ?: capture groups can't be modelled 412 | if (current() == "?") { 413 | next(); 414 | 415 | if (current() != ":") { 416 | throw BuildError("Expected : after ?"); 417 | } 418 | 419 | next(); 420 | capture = false; 421 | } 422 | 423 | let newestCapture = captures.length; 424 | 425 | let atoms = ParseCaptureGroup(captureIndex, capture); 426 | 427 | if (next() != ")") { 428 | throw BuildError("Expected ) (Capture Group Close)"); 429 | } 430 | 431 | if (capture) { 432 | switch (current()) { 433 | case "?": 434 | case "*": 435 | { 436 | //If anything the capture is optional then anything inside it is also optional 437 | //TODO: Take a list of originals and rewrite an implication 438 | //iff Len(origin) > 0 then c[i] = o[i] 439 | for (let i = newestCapture; i < captures.length; i++) { 440 | rewriteCaptureOptional(i); 441 | } 442 | 443 | buildStarConstraints(atoms, newestCapture); 444 | break; 445 | } 446 | case "{": 447 | case "+": 448 | { 449 | buildPlusConstraints(atoms, newestCapture); 450 | break; 451 | } 452 | default: 453 | { 454 | addToCapture(captureIndex, captures[newestCapture]); 455 | break; 456 | } 457 | } 458 | } 459 | 460 | shouldAddFillerIn = false; 461 | 462 | return atoms; 463 | } else { 464 | return ParseMaybeRange(captureIndex); 465 | } 466 | } 467 | 468 | function ParseMaybeAssertion(captureIndex) { 469 | 470 | if (current() == "(" && peek() == "?" && (peek(2) == "!" || peek(2) == "=")) { 471 | const end = FindClosingParen(regex, idx + 3); 472 | const re = regex.slice(idx + 3, end); 473 | pp_steps.push({ type: peek(2), re: re, idx: end + 1 }); 474 | idx = end + 1; 475 | } 476 | 477 | return ParseMaybeCaptureGroupStart(captureIndex); 478 | } 479 | 480 | function ParseMaybePSQ(captureIndex) { 481 | 482 | let atom = ParseMaybeAssertion(captureIndex); 483 | 484 | if (current() == "*") { 485 | next(); 486 | if (current() == "?") { next(); } 487 | atom = ctx.mkReStar(atom); 488 | } else if (current() == "+") { 489 | next(); 490 | if (current() == "?") { next(); } 491 | atom = ctx.mkRePlus(atom); 492 | } else if (current() == "?") { 493 | next(); 494 | if (current() == "?") { next(); } 495 | atom = ctx.mkReOption(atom); 496 | } 497 | 498 | return atom; 499 | } 500 | 501 | function digit(offset) { 502 | if (typeof(offset) === "undefined") { 503 | offset = 0; 504 | } 505 | return peek(offset) >= "0" && peek(offset) <= "9"; 506 | } 507 | 508 | function ParseNumber() { 509 | 510 | 511 | let numStr = ""; 512 | 513 | if (!digit()) { 514 | throw BuildError("Expected Digit (Parse Number)"); 515 | } 516 | 517 | while (digit()) { 518 | numStr += next(); 519 | } 520 | 521 | return parseInt(numStr); 522 | } 523 | 524 | function ParseLoopCount() { 525 | let n1 = ParseNumber(); 526 | 527 | if (current() == ",") { 528 | next(); 529 | 530 | if (!digit()) { 531 | //Either a syntax error or a min loop, assume a min loop 532 | return [n1, undefined]; 533 | } else { 534 | let n2 = ParseNumber(); 535 | return [n1, n2]; 536 | } 537 | } else { 538 | return [n1, n1]; 539 | } 540 | } 541 | 542 | function ParseMaybeLoop(captureIndex) { 543 | let atom = ParseMaybePSQ(captureIndex); 544 | 545 | if (current() == "{" && digit(1)) { 546 | next(); 547 | 548 | let [lo, hi] = ParseLoopCount(); 549 | 550 | if (!next() == "}") { 551 | throw BuildError("Expected } following loop count"); 552 | } 553 | 554 | //Discard any succeeding ? 555 | if (current() == "?") { next(); } 556 | 557 | //If hi is undefined then it's a min loop {5,} 558 | if (hi === undefined) { 559 | atom = ctx.mkReConcat(ctx.mkReLoop(atom, lo, lo), ctx.mkReStar(atom)); 560 | } else { 561 | atom = ctx.mkReLoop(atom, lo, hi); 562 | } 563 | } 564 | 565 | return atom; 566 | } 567 | 568 | function ParseMaybeAtoms(captureIndex) { 569 | 570 | let rollup = null; 571 | 572 | while (more()) { 573 | 574 | while (current() == "^" || current() == "$") { 575 | //TODO: Find out how to handle multiline 576 | next(); 577 | } 578 | 579 | //TODO: This is horrible, anchors should be better 580 | if (more()) { 581 | 582 | //let capturesStart = captures.length; 583 | let parsed = ParseMaybeLoop(captureIndex); 584 | 585 | if (shouldAddFillerIn) { 586 | addToCapture(captureIndex, symbolIn(parsed)); 587 | } 588 | 589 | shouldAddFillerIn = true; 590 | 591 | rollup = rollup ? ctx.mkReConcat(rollup, parsed) : parsed; 592 | } 593 | } 594 | 595 | return rollup || mk(""); 596 | } 597 | 598 | function either(v, left, right) { 599 | assertions.push(ctx.mkOr(ctx.mkEq(v, left), ctx.mkEq(v, right))); 600 | return v; 601 | } 602 | 603 | function buildAlternationCaptureConstraints(captureIndex, startCaptures, endLeftCaptures, endRightCaptures, cLeft, cRight) { 604 | let leftCaptures = []; 605 | let leftOriginals = []; 606 | let rightCaptures = []; 607 | let rightOriginals = []; 608 | 609 | for (let i = startCaptures; i < endLeftCaptures; i++) { 610 | leftOriginals.push(captures[i]); 611 | rewriteCaptureOptional(i); 612 | leftCaptures.push(captures[i]); 613 | } 614 | 615 | for (let i = endLeftCaptures; i < endRightCaptures; i++) { 616 | rightOriginals.push(captures[i]); 617 | rewriteCaptureOptional(i); 618 | rightCaptures.push(captures[i]); 619 | } 620 | 621 | let cFinal = nextFiller(); 622 | 623 | function buildSide(side, left, original, right) { 624 | let forceRightNothing = right.map(x => ctx.mkEq(x, ctx.mkString(""))); 625 | let forceLeftOriginal = left.map((x, idx) => ctx.mkEq(left[idx], original[idx])); 626 | assertions.push(ctx.mkImplies(ctx.mkEq(cFinal, side), ctx.mkAndList(forceRightNothing.concat(forceLeftOriginal)))); 627 | } 628 | 629 | buildSide(cLeft, leftCaptures, leftOriginals, rightCaptures); 630 | buildSide(cRight, rightCaptures, rightOriginals, leftCaptures); 631 | 632 | either(cFinal, cLeft, cRight); 633 | 634 | captures[captureIndex] = cFinal; 635 | } 636 | 637 | function ParseMaybeOption(captureIndex) { 638 | 639 | //Track the length of captures through parsing of either side 640 | //If it changes then the blocks parsed have a capture in them 641 | //and will need extra constraints 642 | let startCaptures = captures.length; 643 | 644 | //The captures on an option are a bit tricky 645 | //The capture is either going to be the current C0 + [Some stuff] | [Some Stuff] 646 | //So we parse one side, reset the capture to cStart, then parse the other and express 647 | //the final constraint as an or of the two 648 | let cStart = captures[captureIndex]; 649 | captures[captureIndex] = ctx.mkString(""); 650 | 651 | let ast = ParseMaybeAtoms(captureIndex); 652 | 653 | //Track the end of the left captures 654 | let endLeftCaptures = captures.length; 655 | 656 | let cLeft = captures[captureIndex]; 657 | 658 | if (current() == "|") { 659 | captures[captureIndex] = ctx.mkString(""); 660 | next(); 661 | 662 | let ast2 = ParseMaybeOption(captureIndex); 663 | 664 | let cRight = captures[captureIndex]; 665 | 666 | let endRightCaptures = captures.length; 667 | 668 | //If any capture groups have been defined in the alternation we need to 669 | //build some new string constraints on the result 670 | buildAlternationCaptureConstraints(captureIndex, startCaptures, endLeftCaptures, endRightCaptures, ctx.mkSeqConcat([cStart, cLeft]), ctx.mkSeqConcat([cStart, cRight])); 671 | 672 | ast = ctx.mkReUnion(ast, ast2); 673 | } else { 674 | captures[captureIndex] = ctx.mkSeqConcat([cStart, cLeft]); 675 | } 676 | 677 | return ast; 678 | } 679 | 680 | function ParseCaptureGroup(captureIndex, capture) { 681 | 682 | if (capture) { 683 | captureIndex = captures.length; 684 | previousCaptureAst.push(null); 685 | captures.push(ctx.mkString("")); 686 | } 687 | 688 | let r = ParseMaybeOption(captureIndex); 689 | 690 | if (capture) { 691 | assertions.push(ctx.mkSeqInRe(captures[captureIndex], r)); 692 | previousCaptureAst[captureIndex] = r; 693 | } 694 | 695 | return r; 696 | } 697 | 698 | let ast = ParseCaptureGroup(0, true); 699 | let implier = captures[0]; 700 | 701 | let startIndex; 702 | let anchoredStart = undefined; 703 | let anchoredEnd = undefined; 704 | 705 | if (regex[0] !== "^") { 706 | anchoredStart = nextFiller(); 707 | ast = ctx.mkReConcat(ctx.mkReStar(TruelyAny()), ast); 708 | implier = ctx.mkSeqConcat([anchoredStart, implier]); 709 | startIndex = ctx.mkSeqLength(anchoredStart); 710 | } else { 711 | startIndex = ctx.mkIntVal(0); 712 | } 713 | 714 | if (regex[regex.length - 1] !== "$") { 715 | anchoredEnd = nextFiller(); 716 | ast = ctx.mkReConcat(ast, ctx.mkReStar(TruelyAny())); 717 | implier = ctx.mkSeqConcat([implier, anchoredEnd]); 718 | } 719 | 720 | /** 721 | * All assertions are post-processed into SMT 722 | * This is done be re-parsing a desugared version of the regex and intersecting it with the AST 723 | */ 724 | pp_steps.forEach(item => { 725 | 726 | const ds_lhs = Desugar(regex.substr(0, item.idx)); 727 | const ds_rhs = Desugar(regex.substr(item.idx)); 728 | 729 | const lhs = RegexRecursive(ctx, ds_lhs + "$", 0).ast; 730 | const rhs = RegexRecursive(ctx, "^" + ds_rhs, 0).ast; 731 | 732 | if (item.type == "b" || item.type == "B") { 733 | 734 | //Constants for use in query 735 | const any_string = ctx.mkReStar(TruelyAny()); 736 | 737 | let word = Word(); 738 | let not_word = ctx.mkReComplement(Word()); 739 | 740 | //If B then flip word and not word 741 | if (item.type == "B") { 742 | const t = not_word; 743 | not_word = word; 744 | word = t; 745 | } 746 | 747 | const empty_string = mk(""); 748 | 749 | //L = lhs in .*\W & rhs in \w.* 750 | const l1_w = ctx.mkReConcat(any_string, not_word); 751 | const l1 = ctx.mkReUnion(ctx.mkReIntersect(lhs, l1_w), empty_string); 752 | 753 | const l2_w = ctx.mkReConcat(word, any_string); 754 | const l2 = ctx.mkReIntersect(rhs, l2_w); 755 | 756 | const l = ctx.mkReConcat(l1, l2); 757 | 758 | //R - lhs in .*\w & rhs in \W.* 759 | const r1_w = ctx.mkReConcat(any_string, word); 760 | const r1 = ctx.mkReIntersect(lhs, r1_w); 761 | 762 | const r2_w = ctx.mkReConcat(not_word, any_string); 763 | const r2 = ctx.mkReUnion(ctx.mkReIntersect(rhs, r2_w), empty_string); 764 | 765 | const r = ctx.mkReConcat(r1, r2); 766 | 767 | //Assert ast intersects l | r 768 | ast = ctx.mkReIntersect(ast, ctx.mkReUnion(l, r)); 769 | } else if (item.type == "=" || item.type == "!") { 770 | 771 | //Compute the asserted regex 772 | let assert = RegexRecursive(ctx, "^" + item.re + "$", 0).ast; 773 | assert = ctx.mkReConcat(assert, ctx.mkReStar(TruelyAny())); 774 | 775 | //Negate it if ?! 776 | if (item.type == "!") { 777 | assert = ctx.mkReComplement(assert); 778 | } 779 | 780 | const lr = ctx.mkReConcat(lhs, ctx.mkReIntersect(rhs, assert)); 781 | 782 | ast = ctx.mkReIntersect(ast, lr); 783 | } else { 784 | throw "Currently unsupported"; 785 | } 786 | }); 787 | 788 | //Give unique names to all captures for Ronny 789 | for (let i = 0; i < captures.length; i++) { 790 | const current = captures[i]; 791 | captures[i] = nextFiller(); 792 | assertions.push(ctx.mkEq(captures[i], current)); 793 | } 794 | 795 | //TODO: Fix tagging to be multiline 796 | return { 797 | ast: ast, 798 | implier: implier, 799 | assertions: assertions, 800 | captures: captures, 801 | startIndex: startIndex, 802 | anchoredStart: anchoredStart, 803 | anchoredEnd: anchoredEnd, 804 | backreferences: backreferences, 805 | idx: idx //Return the index so recursion assertions work out 806 | }; 807 | } 808 | 809 | function RegexOuter(ctx, regex) { 810 | try { 811 | return RegexRecursive(ctx, CullOuterRegex("" + regex), 0, false); 812 | } catch (e) { 813 | throw `${e.error ? e.error.toString() : "" + e} ${e.idx} "${e.remaining}" parsing regex "${regex}" ${e.stack}`; 814 | } 815 | } 816 | 817 | export default RegexOuter; 818 | --------------------------------------------------------------------------------