From 5e1f13eb8f166224104dbc3fc1596eba77728bbb Mon Sep 17 00:00:00 2001 From: Blake Loring Date: Wed, 10 May 2017 14:01:03 +0100 Subject: [PATCH] Initial Commit (Release) --- .gitignore | 28 + Analyser/README.md | 12 + Analyser/package.json | 24 + Analyser/postinstall | 2 + Analyser/src/Analyser.js | 80 + Analyser/src/Coverage.js | 58 + Analyser/src/External.js | 22 + Analyser/src/FunctionModels.js | 178 ++ Analyser/src/NotAnErrorException.js | 15 + Analyser/src/SpecialFunctions.js | 58 + Analyser/src/SymbolicExecution.js | 611 +++++ Analyser/src/SymbolicHelper.js | 85 + Analyser/src/SymbolicState.js | 427 +++ Analyser/src/Utilities/ArrayHelper.js | 14 + Analyser/src/Utilities/IsNative.js | 49 + Analyser/src/Utilities/Log.js | 25 + Analyser/src/Utilities/ObjectHelper.js | 86 + Analyser/src/Values/WrappedValue.js | 123 + Dashboard/content/css/eui.css | 69 + Dashboard/content/css/photon.css | 2333 +++++++++++++++++ Dashboard/content/css/photon.min.css | 9 + Dashboard/content/fonts/photon-entypo.eot | Bin 0 -> 48644 bytes Dashboard/content/fonts/photon-entypo.svg | 295 +++ Dashboard/content/fonts/photon-entypo.ttf | Bin 0 -> 48456 bytes Dashboard/content/fonts/photon-entypo.woff | Bin 0 -> 30692 bytes Dashboard/content/index.html | 164 ++ Dashboard/content/js/graph.js | 74 + Dashboard/content/js/jquery.js | 4 + Dashboard/content/js/output.js | 98 + Dashboard/content/js/replay.js | 14 + Dashboard/content/js/runner.js | 54 + Dashboard/content/js/summary.js | 30 + Dashboard/content/js/timer.js | 26 + Dashboard/content/js/view.js | 100 + Dashboard/content/replay.html | 41 + Dashboard/package.json | 18 + Dashboard/scripts/plot_data | 31 + Dashboard/scripts/replot | 3 + Dashboard/src/expose.js | 82 + Dashboard/src/expose_executor.js | 52 + Dashboard/src/graph_builder.js | 24 + Dashboard/src/graph_data.js | 63 + Dashboard/src/main.js | 49 + Dashboard/src/output_parser.js | 25 + Dashboard/src/replay.js | 42 + Distributor/README.md | 3 + Distributor/package.json | 20 + Distributor/src/Center.js | 160 ++ Distributor/src/CoverageAggregator.js | 73 + Distributor/src/Distributor.js | 90 + Distributor/src/Spawn.js | 155 ++ ElectronDriver/package.json | 21 + ElectronDriver/src/main.js | 45 + ElectronDriver/test/index.html | 7 + LICENSE.md | 7 + README.md | 74 + Tester/README.md | 11 + Tester/package.json | 18 + Tester/src/Runner.js | 97 + Tester/src/TestRunner.js | 36 + Tester/src/Tester.js | 34 + Tester/src/Walker.js | 38 + expoSE | 41 + lib/Mocker/README.md | 3 + lib/Mocker/package.json | 16 + lib/Mocker/src/Handler.js | 13 + lib/Mocker/src/Mocker.js | 22 + lib/Mocker/src/MockerUtilities.js | 12 + lib/Mocker/src/Network/Examples/LightApi.js | 48 + lib/Mocker/src/Network/Examples/LoginRest.js | 44 + .../src/Network/Examples/LoginRestHarnass.js | 26 + lib/Mocker/src/Network/Network.js | 11 + lib/Mocker/src/Network/NetworkRequest.js | 20 + lib/Mocker/src/Network/NetworkState.js | 23 + lib/Mocker/src/Polyfills/ArrayFind.js | 35 + lib/Mocker/src/Polyfills/NoQuery.js | 78 + lib/Mocker/src/Polyfills/NodeDocument.js | 32 + lib/Mocker/src/Polyfills/NodeHttpRequest.js | 36 + lib/Mocker/src/Polyfills/NodeNavigator.js | 13 + lib/Mocker/src/Polyfills/NodePolyfills.js | 15 + lib/Mocker/src/Polyfills/NodeWindow.js | 20 + lib/Mocker/src/Polyfills/Polyfills.js | 15 + lib/Mocker/src/Polyfills/StartsWith.js | 14 + lib/Mocker/src/State.js | 19 + lib/S$/README.md | 0 lib/S$/package.json | 16 + lib/S$/src/symbols.js | 249 ++ lib/Tropigate/README.md | 8 + lib/Tropigate/build_tropigate | 4 + lib/Tropigate/install | 4 + lib/Tropigate/package.json | 21 + lib/Tropigate/run_tropigate | 2 + lib/Tropigate/scripts/abspath | 3 + lib/Tropigate/src/Expression.js | 35 + lib/Tropigate/src/FunctionSignatures.js | 58 + lib/Tropigate/src/Generator.js | 349 +++ lib/Tropigate/src/InjectHelper.js | 90 + lib/Tropigate/src/Prelude.js | 25 + lib/Tropigate/src/Statements.js | 111 + lib/Tropigate/src/Tokens.js | 125 + lib/Tropigate/src/Tropigate.js | 19 + lib/Tropigate/src/TypeParser.js | 77 + lib/Tropigate/src/Unary.js | 81 + lib/Tropigate/src/Utils.js | 16 + lib/Tropigate/src/main-cli.js | 21 + lib/Tropigate/src/main.js | 44 + lib/Tropigate/tropigate | 23 + package.json | 11 + scripts/abspath | 3 + scripts/analyse | 10 + scripts/build/build | 6 + scripts/build/build_analyser | 27 + scripts/build/build_libs | 31 + scripts/build/build_strip_rc | 11 + scripts/build/build_tester | 8 + scripts/build/bundle | 6 + scripts/build/strip | 8 + scripts/expose_env | 14 + scripts/lib_path | 2 + scripts/license_all | 2 + scripts/play | 6 + scripts/pre-push | 3 + scripts/relicense | 4 + scripts/run_tests | 15 + scripts/setup/cleanup | 4 + scripts/setup/cleanup_bashprofile | 4 + scripts/setup/install_bashprofile | 3 + scripts/setup/setup | 23 + scripts/setup/setup_packages | 8 + scripts/setup/setup_pkg | 10 + tests/assert/assert.js | 12 + tests/assert/fail_as.js | 14 + tests/assumes/basic.js | 7 + tests/assumes/wrapped_assume.js | 5 + tests/async/settimeout.js | 18 + tests/bool/basic.js | 15 + tests/bool/hello.js | 19 + tests/core/bools.js | 21 + tests/core/lamda.js | 6 + tests/core/recursion.js | 12 + tests/fractions/fraction_one.js | 14 + tests/integers/breaker.js | 6 + tests/integers/coerce.js | 7 + tests/integers/hello.js | 15 + tests/integers/infoflow.js | 46 + tests/integers/lt.js | 11 + tests/integers/mul.js | 9 + tests/loops/loop_alot.js | 13 + tests/named_method/simple.js | 9 + tests/numbers/floor_check.js | 37 + tests/pure/pure_symbol.js | 7 + tests/regex/anchors.js | 11 + tests/regex/hello_regex.js | 11 + tests/regex/hello_regex2.js | 10 + tests/strings/hello_strings.js | 13 + tests/strings/hello_strings2.js | 17 + tests/strings/hello_strings_concat.js | 12 + tests/strings/hello_strings_concat2.js | 8 + tests/strings/hello_strings_len.js | 9 + tests/strings/strings_charat.js | 5 + tests/strings/strings_concat.js | 9 + tests/strings/warning.js | 21 + tests/test_list.js | 70 + 163 files changed, 9141 insertions(+) create mode 100644 .gitignore create mode 100644 Analyser/README.md create mode 100644 Analyser/package.json create mode 100755 Analyser/postinstall create mode 100644 Analyser/src/Analyser.js create mode 100644 Analyser/src/Coverage.js create mode 100644 Analyser/src/External.js create mode 100644 Analyser/src/FunctionModels.js create mode 100644 Analyser/src/NotAnErrorException.js create mode 100644 Analyser/src/SpecialFunctions.js create mode 100644 Analyser/src/SymbolicExecution.js create mode 100644 Analyser/src/SymbolicHelper.js create mode 100644 Analyser/src/SymbolicState.js create mode 100644 Analyser/src/Utilities/ArrayHelper.js create mode 100644 Analyser/src/Utilities/IsNative.js create mode 100644 Analyser/src/Utilities/Log.js create mode 100644 Analyser/src/Utilities/ObjectHelper.js create mode 100644 Analyser/src/Values/WrappedValue.js create mode 100644 Dashboard/content/css/eui.css create mode 100644 Dashboard/content/css/photon.css create mode 100644 Dashboard/content/css/photon.min.css create mode 100644 Dashboard/content/fonts/photon-entypo.eot create mode 100644 Dashboard/content/fonts/photon-entypo.svg create mode 100644 Dashboard/content/fonts/photon-entypo.ttf create mode 100644 Dashboard/content/fonts/photon-entypo.woff create mode 100644 Dashboard/content/index.html create mode 100644 Dashboard/content/js/graph.js create mode 100644 Dashboard/content/js/jquery.js create mode 100644 Dashboard/content/js/output.js create mode 100644 Dashboard/content/js/replay.js create mode 100644 Dashboard/content/js/runner.js create mode 100644 Dashboard/content/js/summary.js create mode 100644 Dashboard/content/js/timer.js create mode 100644 Dashboard/content/js/view.js create mode 100644 Dashboard/content/replay.html create mode 100644 Dashboard/package.json create mode 100755 Dashboard/scripts/plot_data create mode 100755 Dashboard/scripts/replot create mode 100644 Dashboard/src/expose.js create mode 100644 Dashboard/src/expose_executor.js create mode 100644 Dashboard/src/graph_builder.js create mode 100644 Dashboard/src/graph_data.js create mode 100644 Dashboard/src/main.js create mode 100644 Dashboard/src/output_parser.js create mode 100644 Dashboard/src/replay.js create mode 100644 Distributor/README.md create mode 100644 Distributor/package.json create mode 100644 Distributor/src/Center.js create mode 100644 Distributor/src/CoverageAggregator.js create mode 100644 Distributor/src/Distributor.js create mode 100644 Distributor/src/Spawn.js create mode 100644 ElectronDriver/package.json create mode 100644 ElectronDriver/src/main.js create mode 100644 ElectronDriver/test/index.html create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 Tester/README.md create mode 100644 Tester/package.json create mode 100644 Tester/src/Runner.js create mode 100644 Tester/src/TestRunner.js create mode 100644 Tester/src/Tester.js create mode 100644 Tester/src/Walker.js create mode 100755 expoSE create mode 100644 lib/Mocker/README.md create mode 100644 lib/Mocker/package.json create mode 100644 lib/Mocker/src/Handler.js create mode 100644 lib/Mocker/src/Mocker.js create mode 100644 lib/Mocker/src/MockerUtilities.js create mode 100644 lib/Mocker/src/Network/Examples/LightApi.js create mode 100644 lib/Mocker/src/Network/Examples/LoginRest.js create mode 100644 lib/Mocker/src/Network/Examples/LoginRestHarnass.js create mode 100644 lib/Mocker/src/Network/Network.js create mode 100644 lib/Mocker/src/Network/NetworkRequest.js create mode 100644 lib/Mocker/src/Network/NetworkState.js create mode 100644 lib/Mocker/src/Polyfills/ArrayFind.js create mode 100644 lib/Mocker/src/Polyfills/NoQuery.js create mode 100644 lib/Mocker/src/Polyfills/NodeDocument.js create mode 100644 lib/Mocker/src/Polyfills/NodeHttpRequest.js create mode 100644 lib/Mocker/src/Polyfills/NodeNavigator.js create mode 100644 lib/Mocker/src/Polyfills/NodePolyfills.js create mode 100644 lib/Mocker/src/Polyfills/NodeWindow.js create mode 100644 lib/Mocker/src/Polyfills/Polyfills.js create mode 100644 lib/Mocker/src/Polyfills/StartsWith.js create mode 100644 lib/Mocker/src/State.js create mode 100644 lib/S$/README.md create mode 100644 lib/S$/package.json create mode 100644 lib/S$/src/symbols.js create mode 100644 lib/Tropigate/README.md create mode 100755 lib/Tropigate/build_tropigate create mode 100755 lib/Tropigate/install create mode 100644 lib/Tropigate/package.json create mode 100755 lib/Tropigate/run_tropigate create mode 100755 lib/Tropigate/scripts/abspath create mode 100644 lib/Tropigate/src/Expression.js create mode 100644 lib/Tropigate/src/FunctionSignatures.js create mode 100644 lib/Tropigate/src/Generator.js create mode 100644 lib/Tropigate/src/InjectHelper.js create mode 100644 lib/Tropigate/src/Prelude.js create mode 100644 lib/Tropigate/src/Statements.js create mode 100644 lib/Tropigate/src/Tokens.js create mode 100644 lib/Tropigate/src/Tropigate.js create mode 100644 lib/Tropigate/src/TypeParser.js create mode 100644 lib/Tropigate/src/Unary.js create mode 100755 lib/Tropigate/src/Utils.js create mode 100644 lib/Tropigate/src/main-cli.js create mode 100644 lib/Tropigate/src/main.js create mode 100755 lib/Tropigate/tropigate create mode 100644 package.json create mode 100755 scripts/abspath create mode 100755 scripts/analyse create mode 100755 scripts/build/build create mode 100755 scripts/build/build_analyser create mode 100755 scripts/build/build_libs create mode 100755 scripts/build/build_strip_rc create mode 100755 scripts/build/build_tester create mode 100755 scripts/build/bundle create mode 100755 scripts/build/strip create mode 100755 scripts/expose_env create mode 100755 scripts/lib_path create mode 100755 scripts/license_all create mode 100755 scripts/play create mode 100755 scripts/pre-push create mode 100755 scripts/relicense create mode 100755 scripts/run_tests create mode 100755 scripts/setup/cleanup create mode 100755 scripts/setup/cleanup_bashprofile create mode 100755 scripts/setup/install_bashprofile create mode 100755 scripts/setup/setup create mode 100755 scripts/setup/setup_packages create mode 100755 scripts/setup/setup_pkg create mode 100644 tests/assert/assert.js create mode 100644 tests/assert/fail_as.js create mode 100644 tests/assumes/basic.js create mode 100644 tests/assumes/wrapped_assume.js create mode 100644 tests/async/settimeout.js create mode 100644 tests/bool/basic.js create mode 100644 tests/bool/hello.js create mode 100644 tests/core/bools.js create mode 100644 tests/core/lamda.js create mode 100644 tests/core/recursion.js create mode 100644 tests/fractions/fraction_one.js create mode 100644 tests/integers/breaker.js create mode 100644 tests/integers/coerce.js create mode 100644 tests/integers/hello.js create mode 100644 tests/integers/infoflow.js create mode 100644 tests/integers/lt.js create mode 100644 tests/integers/mul.js create mode 100644 tests/loops/loop_alot.js create mode 100644 tests/named_method/simple.js create mode 100644 tests/numbers/floor_check.js create mode 100644 tests/pure/pure_symbol.js create mode 100644 tests/regex/anchors.js create mode 100644 tests/regex/hello_regex.js create mode 100644 tests/regex/hello_regex2.js create mode 100644 tests/strings/hello_strings.js create mode 100644 tests/strings/hello_strings2.js create mode 100644 tests/strings/hello_strings_concat.js create mode 100644 tests/strings/hello_strings_concat2.js create mode 100644 tests/strings/hello_strings_len.js create mode 100644 tests/strings/strings_charat.js create mode 100644 tests/strings/strings_concat.js create mode 100644 tests/strings/warning.js create mode 100644 tests/test_list.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9aec78a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +bin/ +node_modules/ +temp_build/ +obj/ +obj-test/ +*_jalangi_*.js* +*.d +*.o +*.dylib +*.a +*configure +*config.log +*config.status +*autom4te.cache +*Makefile +*lib.srcs +*shell.srcs +*npm-debug.log +DS_Store +*paper.synctex.gz +*.aux +*.log +*.pdf +z3.emscripten.js.mem +libz3* +.babelrc +Dashboard/tmp/* +tmp/ \ No newline at end of file diff --git a/Analyser/README.md b/Analyser/README.md new file mode 100644 index 00000000..211171e8 --- /dev/null +++ b/Analyser/README.md @@ -0,0 +1,12 @@ +#Analyser + +The core of the symbolic analyser, executes a test script symbolically and reports any errors + +#Program Output +The final output of the program will be the number of script errors that have occured (through fail()) or the error code magic number +If the result of the run is 0xDEADBAD this indicates an internal error has occured. + +#Expected Paths +The analyser can take an optional integer as the expected path count, if PC != this integer then the program will exit with an additional error (errorCount + 1) to indicate this. + +To run it with an expected PC execute ./expoSE FILENAME --expected pathcount diff --git a/Analyser/package.json b/Analyser/package.json new file mode 100644 index 00000000..d452e511 --- /dev/null +++ b/Analyser/package.json @@ -0,0 +1,24 @@ +{ + "name": "ExpoSE", + "version": "0.0.2", + "description": "Symbolic Execution for JavaScript", + "author": "Blake Loring , Johannes Kinder ", + "readme": "README.md", + "dependencies": { + "browserify": "^14.1.0", + "jalangi": "git+https://github.com/Samsung/jalangi2.git", + "z3javascript": "git+https://github.com/ExpoSEJS/z3javascript.git" + }, + "scripts": { + "postinstall": "./postinstall" + }, + "license": "Apache", + "main": "src/SymbolicExecution.js", + "devDependencies": { + "babel-cli": "^6.18.0", + "babel-plugin-strip-function-call": "^1.0.2", + "babel-preset-babili": "0.0.9", + "babel-preset-es2015": "^6.3.13", + "babel-preset-stage-0": "^6.3.13" + } +} diff --git a/Analyser/postinstall b/Analyser/postinstall new file mode 100755 index 00000000..da8ed10f --- /dev/null +++ b/Analyser/postinstall @@ -0,0 +1,2 @@ +#!/bin/bash -e +exit 0 \ No newline at end of file diff --git a/Analyser/src/Analyser.js b/Analyser/src/Analyser.js new file mode 100644 index 00000000..00086dcf --- /dev/null +++ b/Analyser/src/Analyser.js @@ -0,0 +1,80 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +// do not remove the following comment +// JALANGI DO NOT INSTRUMENT +// +// Symbolic execution analyser entry point + +import SymbolicExecution from './SymbolicExecution'; +import ObjectHelper from './Utilities/ObjectHelper'; +import {ConcolicValue, WrappedValue} from './Values/WrappedValue'; +import NotAnErrorException from './NotAnErrorException'; +import Log from './Utilities/Log'; +import Z3 from 'z3javascript'; +import fs from 'fs'; + +/** + * Set up the methods and classes which ObjectHelper considers safe to string + */ + +ObjectHelper.safe(Z3.Expr.prototype); +ObjectHelper.safe(Z3.Model.prototype); +ObjectHelper.safe(Z3.Context.prototype); +ObjectHelper.safe(WrappedValue.prototype); +ObjectHelper.safe(ConcolicValue.prototype) +ObjectHelper.safe(NotAnErrorException.prototype); + +/** + * End the SafeToString table + */ + +const OUT_PATH = 'EXPOSE_OUT_PATH'; +const COVERAGE_PATH = 'EXPOSE_COVERAGE_PATH'; + +function Default(i, d) { + return process.env[i] || d; +} + +let outFilePath = Default(OUT_PATH, undefined); +let outCoveragePath = Default(COVERAGE_PATH, undefined); + +let input = process.argv[process.argv.length - 1]; + +Log.logHigh('Built with VERY logging enabled'); +Log.logMid('Built with FINE logging enabled'); +Log.log('Built with BASE logging enabled'); +Log.log('Intial Input' + input); + +process.title = 'ExpoSE Play ' + input; + +process.on('disconnect', function() { + Log.log('Premature termination - Parent exit') + process.exit(); +}); + +J$.analysis = new SymbolicExecution(J$, JSON.parse(input), (state, coverage) => { + Log.log("Finished play with PC " + state.pathCondition.map(x => x.ast)); + + let finalOut = { + pc: state.finalPC(), + input: state.finalInput(), + errors: state.errors, + alternatives: state.alternatives() + }; + + if (outCoveragePath) { + fs.writeFileSync(outCoveragePath, JSON.stringify(coverage.end())); + Log.log('Wrote final coverage to ' + outCoveragePath); + } else { + Log.log('No final coverage path supplied'); + } + + if (outFilePath) { + fs.writeFileSync(outFilePath, JSON.stringify(finalOut)); + Log.log('Wrote final output to ' + outFilePath); + } else { + Log.log('No final output path supplied'); + } +}); diff --git a/Analyser/src/Coverage.js b/Analyser/src/Coverage.js new file mode 100644 index 00000000..0e751d7e --- /dev/null +++ b/Analyser/src/Coverage.js @@ -0,0 +1,58 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +class Coverage { + + constructor(sandbox) { + this._sandbox = sandbox; + this._branches = []; + this._branchFilenameMap = []; + } + + end() { + let ret = {}; + for (let i = 0; i < this._branches.length; i++) { + if (this._branches[i] !== undefined) { + + //Deep copy the smap + let map = JSON.parse(JSON.stringify(this._sandbox.smap[i+1])); + + //Strip away any non SID related entities + //Also replace all source index arrays to a single value to reduce stdout + for (let j in map) { + if (isNaN(parseInt(j))) { + delete map[j]; + } else { + map[j] = 1; + } + } + + ret[this._branchFilenameMap[i]] = { + smap: map, + branches: this._branches[i] + }; + } + } + + return ret; + } + + getBranchInfo() { + let branchInfo = this._branches[this._sandbox.sid - 1]; + + if (!branchInfo) { + branchInfo = {}; + this._branches[this._sandbox.sid - 1] = branchInfo; + this._branchFilenameMap[this._sandbox.sid - 1] = this._sandbox.smap[this._sandbox.sid].originalCodeFileName; + } + + return branchInfo; + } + + touch(iid) { + this.getBranchInfo()[iid] = 1; + } +} + +export default Coverage; diff --git a/Analyser/src/External.js b/Analyser/src/External.js new file mode 100644 index 00000000..28ec6917 --- /dev/null +++ b/Analyser/src/External.js @@ -0,0 +1,22 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +/** + * This is a function that detects whether we are using Electron and + * if so does a remote call through IPC rather than a direct require. + */ + + const ELECTRON_PATH_MAGIC = '/electron/'; + +export default function (library) { + let lrequire = require; + + if (process && process.execPath && process.execPath.indexOf(ELECTRON_PATH_MAGIC) != -1) { + lrequire = require('electron').remote.require; + } + + const result = lrequire(library); + + return result.default ? result.default : result; +} diff --git a/Analyser/src/FunctionModels.js b/Analyser/src/FunctionModels.js new file mode 100644 index 00000000..5fb53fdc --- /dev/null +++ b/Analyser/src/FunctionModels.js @@ -0,0 +1,178 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import ObjectHelper from './Utilities/ObjectHelper'; +import Log from './Utilities/Log'; +import Z3 from 'z3javascript'; +import {WrappedValue, ConcolicValue} from './Values/WrappedValue'; + +function BuildModels() { + let models = {}; + + for (let item in Object.getOwnPropertyNames(Object.prototype)) { + if (!ObjectHelper.startsWith(item, '__')) { + delete models[item]; + } + } + + function RegexTest(regex, real, string) { + + let in_s = this.ctx.mkSeqInRe(this.state.asSymbolic(string), regex.ast); + let in_c = real.test(this.state.getConcrete(string)); + + return new ConcolicValue(in_c, in_s); + } + + function RegexMatch(regex, real, string, result) { + + let in_regex = RegexTest.apply(this, [regex, real, string, result]); + + //Mock the symbolic conditional if (regex.test(/.../) then regex.match => true) + this.state.symbolicConditional(in_regex); + + regex.assertions.forEach(binder => this.state.pushBinder(binder)); + this.state.pushBinder(this.ctx.mkImplies(this.ctx.mkSeqInRe(this.state.getSymbolic(string), regex.ast), this.ctx.mkEq(this.state.getSymbolic(string), regex.implier))); + + if (result) { + result = result.map((current_c, idx) => + typeof current_c == 'string' ? new ConcolicValue(current_c, regex.captures[idx]) : undefined + ) + } + + return result; + } + + /** + * Symbolic hook is a helper function which builds concrete results and then, + * if condition() -> true executes a symbolic helper specified by hook + * Both hook and condition are called with (context (SymbolicExecutor), f, base, args, result) + * + * A function which makes up the new function model is returned + */ + function symbolicHook(condition, hook) { + return function(f, base, args, result) { + + //Add the methods from Array needed onto the arguments special array type + args.map = Array.prototype.map; + args.find = Array.prototype.find; + + result = f.apply(this.state.getConcrete(base), args.map(arg => this.state.getConcrete(arg))); + + Log.logMid('Symbolic Testing ' + f.name + ' with ' + this); + + //TODO: Bind might be imperformant + if (condition(this, f, base, args, result)) { + Log.logMid('Symbolically modelling ' + f.name); + result = hook(this, f, base, args, result); + } + + return result; + }; + } + + /** + * Model for String(xxx) in code to coerce something to a string + */ + models[String] = symbolicHook( + (c, _f, _base, args, _result) => c.state.isSymbolic(args[0]), + (c, _f, _base, args, result) => new ConcolicValue(result, c.state.asSymbolic(c._concretizeToString(args[0]))) + ); + + models[String.prototype.substr] = symbolicHook( + (c, _f, base, args, _) => c.state.isSymbolic(base) || c.state.isSymbolic(args[0]) || c.state.isSymbolic(args[1]), + (c, _f, base, args, result) => { + Log.log('WARNING: Symbolic substring support new and buggy'); + + let target = c.state.asSymbolic(base); + let start_off = c.ctx.mkRealToInt(c.state.asSymbolic(args[0])); + + let len; + + if (args[1]) { + len = c.state.asSymbolic(args[1]); + len = c.ctx.mkRealToInt(len); + } else { + len = c.ctx.mkSub(c.ctx.mkSeqLength(target), start_off); + } + + return new ConcolicValue(result, c.ctx.mkSeqSubstr(target, start_off, len)); + } + ); + + models[String.prototype.substring] = models[String.prototype.substr]; + + models[String.prototype.charAt] = symbolicHook( + (c, _f, base, args, _r) => c.state.isSymbolic(base) || c.state.isSymbolic(args[0]), + (c, _f, base, args, result) => new ConcolicValue(result, c.ctx.mkSeqAt(c.state.asSymbolic(base), c.ctx.mkRealToInt(c.state.asSymbolic(args[0])))) + ); + + models[String.prototype.concat] = symbolicHook( + (c, _f, base, args, _r) => c.state.isSymbolic(base) || args.find(arg => c.state.isSymbolic(arg)), + (c, _f, base, args, result) => new ConcolicValue(result, c.ctx.mkSeqConcat([c.state.asSymbolic(base)].concat(args.map(arg => c.state.asSymbolic(arg))))) + ); + + models[String.prototype.indexOf] = symbolicHook( + (c, _f, base, args, _r) => c.state.isSymbolic(base) || c.state.isSymbolic(args[0]) || c.state.isSymbolic(args[1]), + (c, _f, base, args, result) => { + let off = args[1] ? c.state.asSymbolic(args[1]) : c.state.asSymbolic(0); + off = c.ctx.mkRealToInt(off); + + //TODO: Rewrite this better + result = new ConcolicValue(result, c.ctx.mkSeqIndexOf(c.state.asSymbolic(base), c.state.asSymbolic(c._concretizeToString(args[0])), off)); + c.state.getSymbolic(result).FORCE_EQ_TO_INT = true; + return result; + } + ); + + models[String.prototype.test] = symbolicHook( + (c, _f, _base, args, _r) => c.state.isSymbolic(args[0]), + (c, _f, base, args, result) => RegexTest.call(c, Z3.Regex(c.ctx, base), base, c._concretizeToString(args[0]), result) + ); + + models[String.prototype.match] = symbolicHook( + (c, _f, base, _a, _r) => c.state.isSymbolic(base), + (c, _f, base, args, result) => RegexMatch.call(c, Z3.Regex(c.ctx, args[0]), args[0], base, result) + ); + + models[String.prototype.exec] = symbolicHook( + (c, _f, _base, args, _r) => c.state.isSymbolic(args[0]), + (c, _f, base, args, result) => RegexMatch.call(c, Z3.Regex(c.ctx, base), base, args[0], result) + ); + + models[String.prototype.replace] = symbolicHook( + (c, _f, base, args, _r) => c.state.isSymbolic(base) && args[0] instanceof RegExp, + (c, _f, base, args, result) => { + Log.log('TODO: Awful String.prototype.replace model will reduce search space'); + let regex = Z3.Regex(c.ctx, args[0]); + c.state.pushBinder(c.ctx.mkNot(c.ctx.mkSeqInRe(c.state.getSymbolic(base), regex.ast))); + return new ConcolicValue(result, c.state.getSymbolic(base)); + } + ); + + models[String.prototype.trim] = symbolicHook( + (c, _f, base, _a, _r) => c.state.isSymbolic(base), + (c, _f, base, _a, result) => { + Log.log('TODO: Trim model does not currently do anything'); + return new ConcolicValue(result, c.state.getSymbolic(base)); + } + ); + + models[String.prototype.toLowerCase] = function(f, base, args, result) { + result = f.apply(this.state.getConcrete(base)); + + if (this.state.isSymbolic(base)) { + Log.log('TODO: Awful String.prototype.toLowerCase model will reduce search space'); + base = this._concretizeToString(base); + let azRegex = Z3.Regex(this.ctx, /^[^A-Z]+$/); + this.state.pushBinder(this.ctx.mkSeqInRe(this.state.getSymbolic(base), azRegex.ast)); + result = new ConcolicValue(result, this.state.getSymbolic(base)); + } + + return result; + }; + + return models; +} + +export default BuildModels(); diff --git a/Analyser/src/NotAnErrorException.js b/Analyser/src/NotAnErrorException.js new file mode 100644 index 00000000..d6878ee0 --- /dev/null +++ b/Analyser/src/NotAnErrorException.js @@ -0,0 +1,15 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +/** + * Thrown by the underlying script when the run should immediately terminate without an error + */ + +class NotAnErrorException { + toString() { + return 'NotAnErrorException'; + } +} + +export default NotAnErrorException; diff --git a/Analyser/src/SpecialFunctions.js b/Analyser/src/SpecialFunctions.js new file mode 100644 index 00000000..953f0a92 --- /dev/null +++ b/Analyser/src/SpecialFunctions.js @@ -0,0 +1,58 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Log from './Utilities/Log'; +import ObjectHelper from './Utilities/ObjectHelper'; +import {WrappedValue, ConcolicValue} from './Values/WrappedValue'; +import NotAnErrorException from './NotAnErrorException'; +import {MarkSafeNative} from './Utilities/IsNative'; + +export default { + wrapSymbolic: function(state, args) { + return new ConcolicValue(args[0], this.state.asSymbolic(args[0])); + }, + + makeSymbolic: function(state, args) { + return state.createSymbolicValue(args[0], args[1]); + }, + + wrap: function(state, args) { + return WrappedValue.wrap(args[0]); + }, + + notAnErrorException: function(state, args) { + return NotAnErrorException; + }, + + markSafeNative: function(state, args) { + MarkSafeNative(args[0]); + }, + + clone: function(state, args) { + return WrappedValue.clone(args[0]); + }, + + getRider: function(state, args) { + let val = args[0]; + + if (!val || !val instanceof WrappedValue) { + Log.log('Could not get rider of unwrapped value (' + ObjectHelper.asString(val) + ')'); + return; + } + + return val.rider; + }, + + setRider: function(state, args) { + let val = args[0]; + let rider = args[1]; + + if (!val instanceof WrappedValue) { + Log.log('Could not set rider of unwrapped value (' + ObjectHelper.asString(val) + ')'); + return; + } + + val.rider = rider; + } +}; diff --git a/Analyser/src/SymbolicExecution.js b/Analyser/src/SymbolicExecution.js new file mode 100644 index 00000000..084f5d8d --- /dev/null +++ b/Analyser/src/SymbolicExecution.js @@ -0,0 +1,611 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +/** + * DART-style symbolic execution engine by Johannes Kinder, Blake Loring (c) 2015 + * + * Based on Jalangi2 ananlysisCallbackTemplate.js by Koushik Sen + */ + +import {WrappedValue, ConcolicValue} from './Values/WrappedValue'; +import SpecialFunctions from './SpecialFunctions'; +import ObjectHelper from './Utilities/ObjectHelper'; +import SymbolicState from './SymbolicState'; +import SymbolicHelper from './SymbolicHelper'; +import Log from './Utilities/Log'; +import Coverage from './Coverage'; +import ArrayHelper from './Utilities/ArrayHelper'; +import NotAnErrorException from './NotAnErrorException'; +import {IsNativeCached} from './Utilities/IsNative'; +import Models from './FunctionModels'; +import External from './External'; + +//Electron can't resolve external librarys directly. The External packages makes that transparent +const Z3 = External('z3javascript'); +const Tropigate = External('Tropigate'); + +//60s default timeout +const DEFAULT_CONTEXT_TIMEOUT = 60000; + +class SymbolicExecution { + + constructor(sandbox, initialInput, exitFn) { + this._sandbox = sandbox; + this._coverage = new Coverage(this._sandbox); + + this._setupSpecialFunctions(); + this._setupState(initialInput); + + this._fileList = new Array(); + this._callStack = new Array(); + + //Bind any uncaught exceptions to the uncaught exception handler + process.on('uncaughtException', this._uncaughtException.bind(this)); + + //Bind the exit handler to the exit callback supplied + process.on('exit', exitFn.bind(null, this.state, this._coverage)); + } + + _setupState(input) { + this.ctx = new Z3.Context(); + this.slv = new Z3.Solver(this.ctx, DEFAULT_CONTEXT_TIMEOUT); + this.state = new SymbolicState(this.ctx, this.slv, input); + } + + _generateErrorOutput(e) { + let baseText = 'Uncaught exception ' + e; + + if (e.stack) { + baseText += ' stack ' + e.stack; + } + + return baseText; + } + + _uncaughtException(e) { + + //Ignore NotAnErrorException + if (e instanceof NotAnErrorException) { + return; + } + + Log.log(this._generateErrorOutput(e)); + + this.state.addError({ + error: '' + e, + stack: e.stack + }); + } + + _setupSpecialFunctions() { + this._specialFunctions = {}; + + for (let item in Object.getOwnPropertyNames(Object.prototype)) { + if (!ObjectHelper.startsWith(item, '__')) { + delete this._specialFunctions[item]; + } + } + + delete this._specialFunctions.toString; + this._specialFunctions['__make__symbolic__'] = SpecialFunctions.makeSymbolic; + this._specialFunctions['__wrap__'] = SpecialFunctions.wrap; + this._specialFunctions['__clone__'] = SpecialFunctions.clone; + this._specialFunctions['__mark_safe_native__'] = SpecialFunctions.markSafeNative; + this._specialFunctions['__not__error_exp__'] = SpecialFunctions.notAnErrorException; + this._specialFunctions['__get__rider__'] = SpecialFunctions.getRider; + this._specialFunctions['__set__rider__'] = SpecialFunctions.setRider; + } + + _isSpecialFunction(f) { + return f.name !== 'toString' && !!this._specialFunctions[f.name]; + } + + _invokeFunAnnotations(result) { + let s_top = this._callStack.pop(); + + WrappedValue.reduceAndDiscard(s_top.base, annotation => { + let fnResult = annotation.invokedOn(s_top.f, s_top.base, s_top.args, result); + result = fnResult.result; + return fnResult.discard; + }); + + return result; + } + + /** + * Get the concrete version of val if symbolic + * If val is an instanceof Object concretize all properties of val + * This is because native functions take arrays or objects with + * symbolic args the args have to be non symbolic + * TODO: Investigate how to allievate this constraint + * TODO: Should do deep property inspection rather than for i in x + */ + _concretize(val) { + val = this.state.getConcrete(val); + + if (val instanceof Array) { + let nval = {}; + + for (let i in val) { + nval[i] = this._concretize(val[i]); + } + + val = nval; + } + + return val; + } + + _invokeFunPreConcretize(f, base, args) { + f = this.state.getConcrete(f); + + if (!(Models[f]) && IsNativeCached(f)) { + Log.logMid("Concrete function " + ObjectHelper.asString(f) + " concretizing all inputs"); + Log.logHigh(ObjectHelper.asString(base) + " " + ObjectHelper.asString(args)); + + base = this.state.getConcrete(base); + + let n_args = new Array(args.length); + + for (let i = 0; i < args.length; i++) { + n_args[i] = this.state.getConcrete(args[i]); + } + + args = n_args; + } + + return { + f: f, + base: base, + args: args, + skip: (f == RegExp.prototype.test) || this._isSpecialFunction(f) + }; + } + + invokeFunPre(iid, f, base, args, isConstructor, isMethod) { + Log.logHigh('Execute function ' + ObjectHelper.asString(f) + ' at ' + this._location(iid)); + + this._callStack.push({ + f: f, + base: base, + args: args + }); + + return this._invokeFunPreConcretize(f, base, args); + } + + _concretizeToString(symbol) { + + if (typeof this.state.getConcrete(symbol) !== 'string') { + //TODO: Fix Me + let cval = '' + this.state.getConcrete(symbol); + Log.log('TODO: Concretizing non string input to test rather than int2string, bool2string etc'); + Log.log('' + symbol + ' reduced to ' + '' + this.state.getConcrete(symbol)); + + symbol = new ConcolicValue(cval, this.state.asSymbolic(cval)); + } + + return symbol; + } + + //Called after a function completes execution + invokeFun(iid, f, base, args, result, isConstructor, isMethod) { + this._coverage.touch(iid); + + Log.logHigh('Exit function (' + ObjectHelper.asString(f) + ') near ' + this._location(iid)); + + if (Models[f]) { + result = Models[f].apply(this, [f, base, args, result]); + } + + if (this._isSpecialFunction(f)) { + Log.logMid('Intercepted call to ' + f.name); + result = this._specialFunctions[f.name](this.state, args, base, f, result); + } + + return { + result: this._invokeFunAnnotations(result) + }; + } + + _fail(args) { + Log.log('====== INTERCEPTED SCRIPT FAILURE ======'); + Log.log('= SCRIPT FAIL WITH ='); + Log.log('PC: ' + this.state.pathCondition); + Log.log('========================================'); + } + + literal(iid, val, hasGetterSetter) { + this._coverage.touch(iid); + return { + result: val + }; + } + + forinObject(iid, val) { + this._coverage.touch(iid); + return { + result: val + }; + } + + _location(iid) { + return this._sandbox.iidToLocation(this._sandbox.getGlobalIID(iid)); + } + + endExpression(iid) { + this._coverage.touch(iid); + } + + declare(iid, name, val, isArgument, argumentIndex, isCatchParam) { + this._coverage.touch(iid); + Log.logHigh('Declare ' + name + ' at ' + this._location(iid)); + return { + result: val + }; + } + + getFieldPre(iid, base, offset, isComputed, isOpAssign, isMethodCall) { + return { + base: base, + offset: offset, + skip: false + }; + } + + getField(iid, base, offset, val, isComputed, isOpAssign, isMethodCall) { + this._coverage.touch(iid); + Log.logHigh('Get field ' + ObjectHelper.asString(base) + '.' + ObjectHelper.asString(offset) + ' at ' + this._location(iid)); + + let result_s = this.state.isSymbolic(base) ? this.state.symbolicField(this.state.getConcrete(base), this.state.asSymbolic(base), this.state.getConcrete(offset), this.state.asSymbolic(offset)) : undefined; + let result_c = this.state.getConcrete(base)[this.state.getConcrete(offset)]; + + WrappedValue.reduceAndDiscard(base, annotation => { + let fnResult = annotation.getField(base, offset, result_c); + result_c = fnResult.result; + return fnResult.discard; + }); + + let result = result_s ? new ConcolicValue(result_c, result_s) : result_c; + + return { + result: result + }; + } + + putFieldPre(iid, base, offset, val, isComputed, isOpAssign) { + + Log.logHigh('Put field ' + ObjectHelper.asString(base) + '.' + ObjectHelper.asString(offset) + ' at ' + this._location(iid)); + + WrappedValue.reduceAndDiscard(base, annotation => { + let fnResult = annotation.setField(base, offset, val); + val = fnResult.result; + return fnResult.discard; + }); + + return { + base: this.state.getConcrete(base), + offset: this.state.getConcrete(offset), + val: val, + skip: false, + }; + } + + putField(iid, base, offset, val, isComputed, isOpAssign) { + this._coverage.touch(iid); + return { + result: val + }; + } + + read(iid, name, val, isGlobal, isScriptLocal) { + this._coverage.touch(iid); + Log.logHigh('Read ' + name + ' at ' + this._location(iid)); + + WrappedValue.reduceAndDiscard(val, + annotation => annotation.isRead(val).discard + ); + + return { + result: val + }; + } + + write(iid, name, val, lhs, isGlobal, isScriptLocal) { + this._coverage.touch(iid); + Log.logHigh('Write ' + name + ' at ' + this._location(iid)); + + WrappedValue.reduceAndDiscard(val, + annotation => annotation.isWritten(val).discard + ); + + return { + result: val + }; + } + + _return(iid, val) { + this._coverage.touch(iid); + + WrappedValue.reduceAndDiscard(val, + annotation => annotation.isReturned(val).discard + ); + + return { + result: val + }; + } + + _throw(iid, val) { + this._coverage.touch(iid); + + return { + result: val + }; + } + + _with(iid, val) { + this._coverage.touch(iid); + return {result: val}; + } + + functionEnter(iid, f, dis, args) { + this._coverage.touch(iid); + Log.logHigh('Entering ' + ObjectHelper.asString(f) + ' ' + this._location(iid)); + } + + functionExit(iid, returnVal, wrappedExceptionVal) { + this._coverage.touch(iid); + + Log.logHigh('Exiting function ' + this._location(iid)); + + return { + returnVal: returnVal, + wrappedExceptionVal: wrappedExceptionVal, + isBacktrack: false + }; + } + + _scriptDepth() { + return this._fileList.length; + } + + _addScript(fd) { + this._fileList.push(fd); + } + + _removeScript() { + return this._fileList.pop(); + } + + scriptEnter(iid, instrumentedFileName, originalFileName) { + this._coverage.touch(iid); + + let enterString = "====== ENTERING SCRIPT " + originalFileName + " depth " + this._scriptDepth() + " ======"; + + if (this._scriptDepth() == 0) { + Log.log(enterString); + } else { + Log.logMid(enterString); + } + + this._addScript(originalFileName); + } + + scriptExit(iid, wrappedExceptionVal) { + this._coverage.touch(iid); + let originalFileName = this._removeScript(); + let exitString = "====== EXITING SCRIPT " + originalFileName + " depth " + this._scriptDepth() + " ======"; + + if (this._scriptDepth() > 0) { + Log.logMid(exitString); + } else { + Log.log(exitString); + } + + return { + wrappedExceptionVal: wrappedExceptionVal, + isBacktrack: false + }; + } + + binaryPre(iid, op, left, right, isOpAssign, isSwitchCaseComparison, isComputed) { + // Don't evaluate natively when args are symbolic + return { + op: op, + left: left, + right: right, + skip: (WrappedValue.isWrapped(left) || WrappedValue.isWrapped(right)) + }; + } + + binary(iid, op, left, right, result_c, isOpAssign, isSwitchCaseComparison, isComputed) { + this._coverage.touch(iid); + Log.logHigh('Op ' + op + ' left ' + ObjectHelper.asString(left) + ' right ' + ObjectHelper.asString(right) + ' result_c ' + ObjectHelper.asString(result_c) + ' at ' + this._location(iid)); + + if (this.state.isSymbolic(left) || this.state.isSymbolic(right)) { + return this._binarySymbolic(op, left, right, result_c); + } else if (this.state.isWrapped(left) || this.state.isWrapped(right)) { + result_c = SymbolicHelper.evalBinary(op, this.state.getConcrete(left), this.state.getConcrete(right)); + } + + return this._binaryNonSymbolic(op, left, right, result_c); + } + + _binaryApplyAnnotations(op, left, right, result) { + + WrappedValue.reduceAndDiscard(left, + annotation => { + let r = annotation.binary(left, right, op, true, result); + result = r.result; + return r.discard; + } + ); + + WrappedValue.reduceAndDiscard(right, + annotation => { + let r = annotation.binary(left, right, op, false, result); + result = r.result; + return r.discard; + } + ); + + return result; + } + + _binaryNonSymbolic(op, left, right, result) { + return { + result: this._binaryApplyAnnotations(op, left, right, result) + }; + } + + _binarySymbolic(op, left, right, result_c) { + + let [left_c, right_c] = [this.state.getConcrete(left), this.state.getConcrete(right)]; + result_c = SymbolicHelper.evalBinary(op, left_c, right_c); + + if (typeof left_c !== typeof right_c) { + Log.log("Concretizing binary " + op + " on operands of differing types. Type coercion not yet implemented symbolically. (" + ObjectHelper.asString(left_c) + ", " + ObjectHelper.asString(right_c) + ') (' + typeof left_c + ', ' + typeof right_c + ')'); + return { + result: result_c + }; + } + + Log.logMid("Symbolically evaluating binary " + op + ", which has concrete result \"" + result_c + "\""); + + //Will automatically wrap non symbolic values + let result = this.state.symbolicBinary(op, left_c, this.state.asSymbolic(left), right_c, this.state.asSymbolic(right)); + result = result ? new ConcolicValue(result_c, result) : result_c; + + return { + result: this._binaryApplyAnnotations(op, left, right, result) + }; + } + + unaryPre(iid, op, left) { + + // Don't evaluate natively when args are symbolic + return { + op: op, + left: left, + skip: WrappedValue.isWrapped(left) + } + } + + unary(iid, op, left, result_c) { + this._coverage.touch(iid); + + Log.logHigh('Unary ' + op + ' left ' + ObjectHelper.asString(left) + ' result ' + ObjectHelper.asString(result_c)); + + if (this.state.isSymbolic(left)) { + return this._unarySymbolic(op, left, result_c); + } else if (this.state.isWrapped(left)) { + result_c = SymbolicHelper.evalUnary(op, this.state.getConcrete(left)); + } + + return this._unaryNonSymbolic(op, left, result_c); + } + + _unaryApplyAnnotations(op, left, result) { + + WrappedValue.reduceAndDiscard(left, + annotation => { + let r = annotation.unary(left, op, result); + result = r.result; + return r.discard; + } + ); + + return result; + } + + _unaryNonSymbolic(op, left, result_c) { + return { + result: this._unaryApplyAnnotations(op, left, result_c) + }; + } + + _unarySymbolic(op, left, result_c) { + + let left_s = this.state.getSymbolic(left); + let left_c = this.state.getConcrete(left); + + result_c = SymbolicHelper.evalUnary(op, this.state.getConcrete(left)); + + Log.logMid("Symbolically evaluating unary " + op + "(" + left_s + "), which has concrete result \"" + result_c + "\""); + + let result_s = this.state.symbolicUnary(op, left_c, left_s); + + let result = result_s ? new ConcolicValue(result_c, result_s) : result_c; + + return { + result: this._unaryApplyAnnotations(op, left, result) + }; + } + + _toBool(val) { + let val_c = this.state.getConcrete(val); + let val_s = this.state.getSymbolic(val); + let result_s = this.state.symbolicCoerceToBool(val_c, val_s); + return result_s ? new ConcolicValue(!!val_c, result_s) : undefined; + } + + conditional(iid, result) { + this._coverage.touch(iid); + + if (this.state.isSymbolic(result)) { + Log.logMid("Evaluating symbolic condition " + this.state.getSymbolic(result) + " at " + this._location(iid)); + result = this._toBool(result); + + if (result) { + this.state.symbolicConditional(result); + } else { + Log.logMid('Concretized ' + result + ' because do not know how to coerce'); + } + } else { + Log.logHigh("Concrete test at " + this._location(iid)); + } + + return { + result: this.state.getConcrete(result) + }; + } + + instrumentCodePre(iid, code) { + + let acornOpts = { + ranges: true, + ecmaVersion: 7, + sourceType: "module" + } + + try { + code = Tropigate(code, acornOpts); + } catch (e) { + throw 'Tropigate failed because ' + e + ' on program ' + code + ' at ' + e.stack; + } + + return { + code: code, + skip: false + }; + } + + instrumentCode(iid, code, newAst) { + return { + result: code + }; + } + + runInstrumentedFunctionBody(iid) { + this._coverage.touch(iid); + return false; + } + + onReady(cb) { + cb(); + } +} + +export default SymbolicExecution; diff --git a/Analyser/src/SymbolicHelper.js b/Analyser/src/SymbolicHelper.js new file mode 100644 index 00000000..09c58907 --- /dev/null +++ b/Analyser/src/SymbolicHelper.js @@ -0,0 +1,85 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Log from './Utilities/Log'; + +export default { + evalBinary: function(op, left, right) { + switch (op) { + case "==": + return (left == right); + case "===": + return (left === right); + case "!=": + return (left != right); + case "!==": + return (left !== right); + + case "<": + return (left < right); + case ">": + return (left > right); + case "<=": + return (left <= right); + case ">=": + return (left >= right); + + case "+": + return (left + right); + case "-": + return (left - right); + case "*": + return (left * right); + case "/": + return (left / right); + case "%": + return (left % right); + + case ">>": + return (left >> right); + case "<<": + return (left << right); + case ">>>": + return (left >>> right); + + case "&&": + return (left && right); + case "||": + return (left || right); + case "&": + return (left & right); + case "|": + return (left | right); + case "^": + return (left ^ right); + + case "instanceof": + return (left instanceof right); + case "in": + return (left in right); + + default: + Log.log("Unsupported binary operator " + op + " for concrete evaluation."); + return undefined; + } + }, + + evalUnary: function(op, left) { + switch (op) { + case "!": + return !left; + case "~": + return ~left; + case "-": + return -left; + case "+": + return +left; + case "typeof": + return typeof left; + default: + Log.log("Unsupported unary operator " + op + " for concrete evaluation."); + return undefined; + } + } +}; diff --git a/Analyser/src/SymbolicState.js b/Analyser/src/SymbolicState.js new file mode 100644 index 00000000..7990c57c --- /dev/null +++ b/Analyser/src/SymbolicState.js @@ -0,0 +1,427 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Z3 from 'z3javascript'; +import Log from './Utilities/Log'; +import ObjectHelper from './Utilities/ObjectHelper'; +import {WrappedValue, ConcolicValue} from './Values/WrappedValue'; + +class SymbolicState { + constructor(context, solver, input) { + + this.ctx = context; + this.slv = solver; + this.input = input; + + this.boolSort = this.ctx.mkBoolSort(); + this.stringSort = this.ctx.mkStringSort(); + this.realSort = this.ctx.mkRealSort(); + + this.inputSymbols = {}; + + this.pathCondition = []; + + this.errors = []; + } + + getErrorCount() { + return this.errors.length; + } + + addError(error) { + this.errors.push(error); + } + + /** + * Push a binder condition to the PC, + * Binder conditions are just conditions that must be true for a PC to hold + * And are not negated to build new paths + */ + pushBinder(cnd) { + this.pathCondition.push({ + ast: cnd, + binder: true + }); + } + + pushCondition(cnd) { + this.pathCondition.push({ + ast: cnd, + binder: false + }); + } + + pushNot(cnd) { + this.pushCondition(this.ctx.mkNot(cnd)); + } + + symbolicConditional(result) { + let [result_s, result_c] = [this.getSymbolic(result), this.getConcrete(result)]; + + if (result_c === true) { + Log.logMid("Concrete result was true, pushing " + result_s); + this.pushCondition(result_s); + } else if (result_c === false) { + Log.logMid("Concrete result was false, pushing not of " + result_s); + this.pushNot(result_s); + } else { + Log.log("Result: " + result_c.toString() + ' and ' + result_s.toString() + " (" + typeof(result_c) + ")"); + Log.log("Undefined result not yet supported"); + } + } + + /** + * Roll PC into a single AND'ed PC + */ + _simplifyPC(pc) { + return pc.reduce((prev, current) => this.ctx.mkAnd(prev, current)).simplify(); + } + + /** + *Formats PC to pretty string if length != 0 + */ + _stringPC(pc) { + if (pc.length) { + return this._simplifyPC(pc).toPrettyString(); + } else { + return ""; + } + } + + /** + * Returns the final PC as a string (if any symbols exist) + */ + finalPC() { + return this._stringPC(this.pathCondition.map(x => x.ast)); + } + + /** + * Regenerate the final input object from the path condition (for output) + * Use initial input if the PC couldn't be satisfied (Some serious issues has occured) + */ + finalInput() { + return this.input; + } + + _buildPC(childInputs, i) { + let newPC = this.ctx.mkNot(this.pathCondition[i].ast).simplify(); + Log.logMid('Checking if ' + ObjectHelper.asString(newPC) + ' is satisfiable'); + + let solution = this._checkSat(newPC); + + if (solution) { + solution._bound = i + 1; + + childInputs.push({ + input: solution, + pc: this._stringPC(newPC) + }); + + Log.logMid("Satisfiable. Remembering new input: " + ObjectHelper.asString(solution)); + } else { + Log.logMid("Unsatisfiable."); + } + } + + alternatives() { + let childInputs = []; + + if (this.input._bound >= this.pathCondition.length) { + Log.log('Bound > PathCondition'); + Log.log('Warning: This path has diverged'); + } + + //Push all PCs up until bound + for (let i = 0; i < Math.min(this.input._bound, this.pathCondition.length); i++) { + this.slv.assert(this.pathCondition[i].ast); + } + + for (let i = this.input._bound; i < this.pathCondition.length; i++) { + + if (!this.pathCondition[i].binder) { + this._buildPC(childInputs, i); + } + + //Push the current thing we're looking at to the solver + this.slv.assert(this.pathCondition[i].ast); + console.log('Asserted ' + this.pathCondition[i].ast.toString()); + } + + this.slv.reset(); + + // Generational search would now Run&Check all new child inputs + return childInputs; + } + + createSymbolicValue(name, concrete) { + + let sort; + + switch (typeof concrete) { + case 'boolean': + sort = this.boolSort; + break; + + case 'number': + sort = this.realSort; + break; + + case 'string': + sort = this.stringSort; + break; + + default: + Log.log("Symbolic input variable of type " + typeof val + " not yet supported."); + } + + let symbol = this.ctx.mkStringSymbol(name); + let symbolic = this.ctx.mkConst(symbol, sort); + + // Use generated input if available + if (name in this.input) { + concrete = this.input[name]; + } else { + this.input[name] = concrete; + } + + this.inputSymbols[name] = symbolic; + + Log.logMid("Initializing fresh symbolic variable \"" + symbolic + "\" using concrete value \"" + concrete + "\""); + return new ConcolicValue(concrete, symbolic); + } + + getSolution(model) { + let solution = {}; + + for (let name in this.inputSymbols) { + let solutionAst = model.eval(this.inputSymbols[name]); + solution[name] = solutionAst.asConstant(); + solutionAst.destroy(); + } + + model.destroy(); + return solution; + } + + _checkSat(clause) { + + let result; + + this.slv.push(); + { + this.slv.assert(clause); + Log.logMid('Solver: ' + this.slv.toString()); + let model = this.slv.getModel(); + result = model ? this.getSolution(model) : undefined; + } + this.slv.pop(); + + return result; + } + + isSymbolic(val) { + return !!ConcolicValue.getSymbolic(val); + } + + getSymbolic(val) { + return ConcolicValue.getSymbolic(val); + } + + isWrapped(val) { + return WrappedValue.isWrapped(val); + } + + getConcrete(val) { + return WrappedValue.getConcrete(val); + } + + asSymbolic(val) { + return this.getSymbolic(val) || this.wrapConstant(val); + } + + getAnnotations(val) { + return WrappedValue.getAnnotations(val); + } + + symbolicBinary(op, left_c, left_s, right_c, right_s) { + + let ctx = this.ctx; + + /** + * TODO: The following code forces coercions to int, rather than + * Letting the default upgrading to reals happen. + * This is awful code to fix a silly bug in Z3 + * Remove ASAP + */ + function coerceInt(s) { + if (!s.FORCE_EQ_TO_INT) { + return ctx.mkRealToInt(s); + } + return s; + } + + let resultIsInt = false; + + if (left_s.FORCE_EQ_TO_INT || right_s.FORCE_EQ_TO_INT) { + left_s = coerceInt(left_s); + right_s = coerceInt(right_s); + resultIsInt = true; + } + + let result; + + switch (op) { + case "===": + case "==": + result = this.ctx.mkEq(left_s, right_s); + break; + case "!==": + case "!=": + result = this.ctx.mkNot(this.ctx.mkEq(left_s, right_s)); + break; + case "&&": + result = this.ctx.mkAnd(left_s, right_s); + break; + case "||": + result = this.ctx.mkOr(left_s, right_s); + break; + case ">": + result = this.ctx.mkGt(left_s, right_s); + break; + case ">=": + result = this.ctx.mkGe(left_s, right_s); + break; + case "<=": + result = this.ctx.mkLe(left_s, right_s); + break; + case "<": + result = this.ctx.mkLt(left_s, right_s); + break; + case "+": + if (typeof left_c == "string") { + result = this.ctx.mkSeqConcat([left_s, right_s]); + } else { + result = this.ctx.mkAdd(left_s, right_s); + } + break; + case "-": + result = this.ctx.mkSub(left_s, right_s); + break; + case "*": + result = this.ctx.mkMul(left_s, right_s); + break; + case "/": + result = this.ctx.mkDiv(left_s, right_s); + break; + case "%": + result = this.ctx.mkMod(left_s, right_s); + break; + default: + Log.log("Symbolic execution does not support operand \"" + op + "\", concretizing."); + return undefined; + } + + //Tags results as int + if (resultIsInt) { + result.FORCE_EQ_TO_INT = true; + } + + return result; + } + + _symbolicFieldStrLookup(base_c, base_s, field_c, field_s) { + Log.log('WARNING: symbolic charAt support new and buggy'); + let lookupCnd = this.ctx.mkNot(this.symbolicBinary('>=', base_c.length, this.symbolicField(base_c, base_s, 'length'), field_c, this.wrapConstant(field_c))); + this.symbolicConditional(new ConcolicValue(base_c.length < field_c, lookupCnd)); + return this.ctx.mkSeqAt(base_s, this.ctx.mkRealToInt(field_s)); + } + + symbolicField(base_c, base_s, field_c, field_s) { + + if (typeof base_c === "string" && typeof field_c === "number") { + return this._symbolicFieldStrLookup(base_c, base_s, field_c, field_s); + } + + switch (field_c) { + case 'length': + if (typeof base_c == "string") { + //TODO: This is a stupid solution to a more fundamental problem in Z3 + //Remove ASAP + let res = this.ctx.mkSeqLength(base_s); + res.FORCE_EQ_TO_INT = true; + return res; + } + default: + Log.log('Unsupported symbolic field - concretizing' + base_c + ' and field ' + field_c); + } + + return undefined; + } + + symbolicCoerceToBool(val_c, val_s) { + let result = undefined; + + if (typeof val_c === "boolean") { + result = val_s; + } else if (typeof val_c === "number") { + result = this.symbolicBinary('!=', val_c, val_s, 0, this.wrapConstant(0)); + } else if (typeof val_c === "string") { + result = this.symbolicBinary('!=', val_c, val_s, "", this.wrapConstant("")); + } else { + Log.logMid('Cannot coerce '+ val_c + ' to boolean'); + } + + return result; + } + + symbolicUnary(op, left_c, left_s) { + switch (op) { + case "!": { + let bool_s = this.symbolicCoerceToBool(left_c, left_s); + return bool_s ? this.ctx.mkNot(bool_s) : undefined; + } + case "+": { + + switch (typeof left_c) { + case 'string': + return this.ctx.mkStrToInt(left_s); + } + + //For numeric types, +N => N + //I don't see this being done often, generally only used to coerce + //But some tit might write var x = +5; + return left_s; + } + case "-": + + switch (typeof left_c) { + case 'string': + Log.log('Casting string to int, if its a real you will get incorrect result'); + return this.ctx.mkStrToInt(left_s); + } + + return this.ctx.mkUnaryMinus(left_s); + case "typeof": + return undefined; + default: + Log.logMid("Unsupported operand: " + op); + return undefined; + } + } + + wrapConstant(val) { + switch (typeof val) { + case 'boolean': + return val ? this.ctx.mkTrue() : this.ctx.mkFalse(); + case 'number': + return Math.round(val) === val ? this.ctx.mkReal(val, 1) : this.ctx.mkNumeral(String(val), this.realSort); + case 'string': + return this.ctx.mkString(val.toString()); + default: + Log.log("Symbolic expressions with " + typeof val + " literals not yet supported."); + } + } +} + +export default SymbolicState; diff --git a/Analyser/src/Utilities/ArrayHelper.js b/Analyser/src/Utilities/ArrayHelper.js new file mode 100644 index 00000000..41b951a0 --- /dev/null +++ b/Analyser/src/Utilities/ArrayHelper.js @@ -0,0 +1,14 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +export default { + add: function(array, item) { + array.push(item); + return array; + }, + + addIf: function(condition, array, item) { + return condition ? ArrayHelper.add(array, item) : array; + } +}; diff --git a/Analyser/src/Utilities/IsNative.js b/Analyser/src/Utilities/IsNative.js new file mode 100644 index 00000000..25562cf9 --- /dev/null +++ b/Analyser/src/Utilities/IsNative.js @@ -0,0 +1,49 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +//Some code is from https://gist.github.com/jdalton/5e34d890105aca44399f by John-David Dalton + +const toString = Object.prototype.toString; +const fnToString = Function.prototype.toString; +const reHostCtor = /^\[object .+?Constructor\]$/; +const SECRET_CACHE_STR = '__checked_isNative__before__'; + +var reNative = RegExp('^' + + String(toString) + .replace(/[.*+?^${}()|[\]\/\\]/g, '\\$&') + .replace(/toString|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' +); + +function isNative(value) { + var type = typeof value; + return type == 'function' ? reNative.test(fnToString.call(value)) : + (value && type == 'object' && reHostCtor.test(toString.call(value))) || false; +} + +let nativeCache = {}; + +/** + * Override a method in the nativeCache to a specific state (safe, not safe) + * TODO: Now that FunctionModels exists, that would probably be a better place + */ +function MarkSafeNative(fn, safe) { + nativeCache[fn] = safe; +} + +/** + * Check nativeCache to see if method has been cached + * If miss, isNative, store, return + * Else return cache + */ +function IsNativeCached(value) { + if (nativeCache[value] !== undefined) { + return nativeCache[value]; + } else { + let result = isNative(value); + nativeCache[value] = result; + return result; + } +} + +export {MarkSafeNative, IsNativeCached}; diff --git a/Analyser/src/Utilities/Log.js b/Analyser/src/Utilities/Log.js new file mode 100644 index 00000000..d9c7c690 --- /dev/null +++ b/Analyser/src/Utilities/Log.js @@ -0,0 +1,25 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +/** + * Class to handle logging + * Structured this way for historical reasons, unneeded + * logs are now removed at compile for performance + */ + +class Log { + logHigh(msg) { + console.log('ExpoSE HIGH: ' + msg); + } + + logMid(msg) { + console.log('ExpoSE MID: ' + msg); + } + + log(msg) { + console.log('ExpoSE: ' + msg); + } +} + +export default new Log(); diff --git a/Analyser/src/Utilities/ObjectHelper.js b/Analyser/src/Utilities/ObjectHelper.js new file mode 100644 index 00000000..f5b56bd3 --- /dev/null +++ b/Analyser/src/Utilities/ObjectHelper.js @@ -0,0 +1,86 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +const MAX_LOG_DEPTH = 2; + +class ObjectHelper {} + +ObjectHelper.functionName = function(fn) { + return fn.name || "anonymous"; +} + +ObjectHelper.safe = function(item) { + item.__safe_item_to_string = true; +} + +ObjectHelper.isSafe = function(item) { + return item && item.__safe_item_to_string; +} + +ObjectHelper.startsWith = function(str1, searchString){ + return str1.substr(0, searchString.length) === searchString; +}; + +ObjectHelper.repeat = function(str, count) { + let repeatedStr = ''; + while (count > 0) { + repeatedStr += str; + count--; + } + return repeatedStr; +} + +ObjectHelper.enumerate = function(item, depth) { + if (depth < MAX_LOG_DEPTH) { + if (item instanceof Array) { + return '[' + item.reduce((last, next) => last + (last.length == 0 ? '' : ', ') + ObjectHelper.asString(next, false, depth + 1), "") + ']'; + } else if (item instanceof Object) { + let result = "{"; + let first = true; + for (let property in item) { + result += (first ? "" : ",") + '\n' + ObjectHelper.repeat(' ', depth + 1) + property + ': ' + ObjectHelper.asString(item[property], false, depth + 1); + first = false; + } + result += '\n}'; + return result; + } + } else { + return 'Max Depth'; + } + + return 'Unstringable'; +} + +ObjectHelper.asString = function(item, forceSafe, depth) { + + //If depth is undefined make it 0 + depth = depth || 0; + + if (item instanceof Function) { + return ObjectHelper.functionName(item); + } + + if (typeof item === 'number' || typeof item === 'boolean' || typeof item === 'string' || item === undefined || item === null) { + return '' + item; + } + + if (forceSafe || ObjectHelper.isSafe(item)) { + return item.toString(); + } else { + return ObjectHelper.enumerate(item, depth); + } +} + +ObjectHelper.asConjunction = function(item, depth) { + depth = depth || 0; + if (depth < MAX_LOG_DEPTH) { + if (item instanceof Array) { + return item.reduce((last, next) => last + (last.length == 0 ? '' : ' ∧ ') + next.toPrettyString(), ""); + } else { + return item.toPrettyString(); + } + } +} + +export default ObjectHelper; diff --git a/Analyser/src/Values/WrappedValue.js b/Analyser/src/Values/WrappedValue.js new file mode 100644 index 00000000..0f87d9a8 --- /dev/null +++ b/Analyser/src/Values/WrappedValue.js @@ -0,0 +1,123 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import ArrayHelper from '../Utilities/ArrayHelper'; + +/** + * Wrapped (shadow-value) class by Blake Loring (c) 2015 + */ +class WrappedValue { + + constructor(concrete) { + this.concrete = concrete; + this.rider = null; + } + + getAnnotations() { + return this.rider ? this.rider.params() : []; + } + + discardAnnotation(annotation) { + + let index = this.getAnnotations().indexOf(annotation); + + if (index != -1) { + this.getAnnotations().splice(index, 1); + annotation.discarded(this.concrete); + } + + return this; + } + + _discardAnnotations(toRemove) { + toRemove.forEach(annotation => this.discardAnnotation(annotation)); + return this; + } + + _reduceAnnotations(fn) { + return this.getAnnotations().reduce( + (list, annotation) => ArrayHelper.addIf(fn(annotation), list, annotation), [] + ); + } + + reduceAndDiscard(fn) { + this._discardAnnotations(this._reduceAnnotations(fn)); + return this; + } + + clone() { + return new WrappedValue(this.concrete); + } + + toString() { + return 'Wrapped(' + this.concrete + ', ' + (this.rider ? this.rider.toString() : '') + ')'; + } + + valueOf() { + return this.concrete ? this.concrete.valueOf() : this.concrete; + } +} + +WrappedValue.isWrapped = function(val) { + return val instanceof WrappedValue; +} + +WrappedValue.clone = function(val) { + return val instanceof WrappedValue ? val.clone() : val; +} + +WrappedValue.getConcrete = function(val) { + return WrappedValue.isWrapped(val) ? val.concrete : val; +} + +WrappedValue.getAnnotations = function(val) { + return WrappedValue.isWrapped(val) ? val.getAnnotations() : []; +} + +/** + * Reduce and discard annotations based on the predicate fn from the given value + */ +WrappedValue.reduceAndDiscard = (val, fn) => val instanceof WrappedValue && val.reduceAndDiscard(fn); + +/** + * If the passed value is already wrapped return value otherwise return a new wrapped version of the value + */ +WrappedValue.wrap = val => val instanceof WrappedValue ? val : new WrappedValue(val); + +/* + * Copyright 2013 Samsung Information Systems America, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Author: Koushik Sen +// ES6 Translation: Blake Loring + +class ConcolicValue extends WrappedValue { + constructor(concrete, symbolic) { + super(concrete); + this.symbolic = symbolic; + } + + toString() { + return 'Concolic(' + this.concrete + ', ' + this.symbolic +', ' + (this.rider ? this.rider.toString() : '') + ')'; + } + + clone() { + return new ConcolicValue(this.concrete, this.symbolic); + } +} + +ConcolicValue.getSymbolic = (val) => val instanceof ConcolicValue ? val.symbolic : undefined; + +export {WrappedValue, ConcolicValue}; diff --git a/Dashboard/content/css/eui.css b/Dashboard/content/css/eui.css new file mode 100644 index 00000000..61afebab --- /dev/null +++ b/Dashboard/content/css/eui.css @@ -0,0 +1,69 @@ +.graph { + width: 100%; + display: flex; +} + +.nomargin { + margin: 0; +} + +.pane { + min-height: 100%; + display: flex; + flex-direction: column; +} + +.flex { + flex: 1; +} + + +#summary, #execute_results { + width: 100%; + max-width: 100%; + overflow-y: auto; +} + +#cancelbtn { + display: none; +} + +#analyze_pane { + display: none; +} + +#output_pane { + display: none; +} + +#errors_pane { + display: none; +} + +#testcases_pane { + display: none; +} + +.toolbar-footer { + min-height: 50px; +} + +#execution_output { + width: 100%; + max-width: 100%; + height: 100%; + display: block; + overflow-y: auto; +} + +#execution_output td { + white-space: normal; +} + +#graph_content { + width: 100%; +} + +#timer { + display: inline; +} \ No newline at end of file diff --git a/Dashboard/content/css/photon.css b/Dashboard/content/css/photon.css new file mode 100644 index 00000000..f198b1fb --- /dev/null +++ b/Dashboard/content/css/photon.css @@ -0,0 +1,2333 @@ +/*! + * ===================================================== + * Photon v0.1.1 + * Copyright 2015 Connor Sears + * Licensed under MIT (https://github.com/connors/proton/blob/master/LICENSE) + * + * v0.1.1 designed by @connors. + * ===================================================== + */ + +@charset "UTF-8"; +audio, +canvas, +progress, +video { + vertical-align: baseline; +} + +audio:not([controls]) { + display: none; +} + +a:active, +a:hover { + outline: 0; +} + +abbr[title] { + border-bottom: 1px dotted; +} + +b, +strong { + font-weight: bold; +} + +dfn { + font-style: italic; +} + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +small { + font-size: 80%; +} + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +pre { + overflow: auto; +} + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; +} + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +input[type="search"] { + -webkit-appearance: textfield; + box-sizing: content-box; +} + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +legend { + border: 0; + padding: 0; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} + +* { + cursor: default; + -webkit-user-drag: text; + -webkit-user-select: none; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +html { + height: 100%; + width: 100%; + overflow: hidden; +} + +body { + height: 100%; + padding: 0; + margin: 0; + font-family: system, -apple-system, ".SFNSDisplay-Regular", "Helvetica Neue", Helvetica, "Segoe UI", sans-serif; + font-size: 13px; + line-height: 1.6; + color: #333; + background-color: transparent; +} + +hr { + margin: 15px 0; + overflow: hidden; + background: transparent; + border: 0; + border-bottom: 1px solid #ddd; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 20px; + margin-bottom: 10px; + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +h1 { + font-size: 36px; +} + +h2 { + font-size: 30px; +} + +h3 { + font-size: 24px; +} + +h4 { + font-size: 18px; +} + +h5 { + font-size: 14px; +} + +h6 { + font-size: 12px; +} + +.window { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: flex; + flex-direction: column; + background-color: #fff; +} + +.window-content { + position: relative; + overflow-y: auto; + display: flex; + flex: 1; +} + +.selectable-text { + cursor: text; + -webkit-user-select: text; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.text-left { + text-align: left; +} + +.pull-left { + float: left; +} + +.pull-right { + float: right; +} + +.padded { + padding: 10px; +} + +.padded-less { + padding: 5px; +} + +.padded-more { + padding: 20px; +} + +.padded-vertically { + padding-top: 10px; + padding-bottom: 10px; +} + +.padded-vertically-less { + padding-top: 5px; + padding-bottom: 5px; +} + +.padded-vertically-more { + padding-top: 20px; + padding-bottom: 20px; +} + +.padded-horizontally { + padding-right: 10px; + padding-left: 10px; +} + +.padded-horizontally-less { + padding-right: 5px; + padding-left: 5px; +} + +.padded-horizontally-more { + padding-right: 20px; + padding-left: 20px; +} + +.padded-top { + padding-top: 10px; +} + +.padded-top-less { + padding-top: 5px; +} + +.padded-top-more { + padding-top: 20px; +} + +.padded-bottom { + padding-bottom: 10px; +} + +.padded-bottom-less { + padding-bottom: 5px; +} + +.padded-bottom-more { + padding-bottom: 20px; +} + +.sidebar { + background-color: #f5f5f4; +} + +.draggable { + -webkit-app-region: drag; +} + +.clearfix:before, .clearfix:after { + display: table; + content: " "; +} +.clearfix:after { + clear: both; +} + +.btn { + display: inline-block; + padding: 3px 8px; + margin-bottom: 0; + font-size: 12px; + line-height: 1.4; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: default; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.06); + -webkit-app-region: no-drag; +} +.btn:focus { + outline: none; + box-shadow: none; +} + +.btn-mini { + padding: 2px 6px; +} + +.btn-large { + padding: 6px 12px; +} + +.btn-form { + padding-right: 20px; + padding-left: 20px; +} + +.btn-default { + color: #333; + border-top-color: #c2c0c2; + border-right-color: #c2c0c2; + border-bottom-color: #a19fa1; + border-left-color: #c2c0c2; + background-color: #fcfcfc; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fcfcfc), color-stop(100%, #f1f1f1)); + background-image: -webkit-linear-gradient(top, #fcfcfc 0%, #f1f1f1 100%); + background-image: linear-gradient(to bottom, #fcfcfc 0%, #f1f1f1 100%); +} +.btn-default:active { + background-color: #ddd; + background-image: none; +} + +.btn-primary, +.btn-positive, +.btn-negative, +.btn-warning { + color: #fff; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); +} + +.btn-primary { + border-color: #388df8; + border-bottom-color: #0866dc; + background-color: #6eb4f7; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #6eb4f7), color-stop(100%, #1a82fb)); + background-image: -webkit-linear-gradient(top, #6eb4f7 0%, #1a82fb 100%); + background-image: linear-gradient(to bottom, #6eb4f7 0%, #1a82fb 100%); +} +.btn-primary:active { + background-color: #3e9bf4; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #3e9bf4), color-stop(100%, #0469de)); + background-image: -webkit-linear-gradient(top, #3e9bf4 0%, #0469de 100%); + background-image: linear-gradient(to bottom, #3e9bf4 0%, #0469de 100%); +} + +.btn-positive { + border-color: #29a03b; + border-bottom-color: #248b34; + background-color: #5bd46d; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bd46d), color-stop(100%, #29a03b)); + background-image: -webkit-linear-gradient(top, #5bd46d 0%, #29a03b 100%); + background-image: linear-gradient(to bottom, #5bd46d 0%, #29a03b 100%); +} +.btn-positive:active { + background-color: #34c84a; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #34c84a), color-stop(100%, #248b34)); + background-image: -webkit-linear-gradient(top, #34c84a 0%, #248b34 100%); + background-image: linear-gradient(to bottom, #34c84a 0%, #248b34 100%); +} + +.btn-negative { + border-color: #fb2f29; + border-bottom-color: #fb1710; + background-color: #fd918d; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fd918d), color-stop(100%, #fb2f29)); + background-image: -webkit-linear-gradient(top, #fd918d 0%, #fb2f29 100%); + background-image: linear-gradient(to bottom, #fd918d 0%, #fb2f29 100%); +} +.btn-negative:active { + background-color: #fc605b; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fc605b), color-stop(100%, #fb1710)); + background-image: -webkit-linear-gradient(top, #fc605b 0%, #fb1710 100%); + background-image: linear-gradient(to bottom, #fc605b 0%, #fb1710 100%); +} + +.btn-warning { + border-color: #fcaa0e; + border-bottom-color: #ee9d02; + background-color: #fece72; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fece72), color-stop(100%, #fcaa0e)); + background-image: -webkit-linear-gradient(top, #fece72 0%, #fcaa0e 100%); + background-image: linear-gradient(to bottom, #fece72 0%, #fcaa0e 100%); +} +.btn-warning:active { + background-color: #fdbc40; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fdbc40), color-stop(100%, #ee9d02)); + background-image: -webkit-linear-gradient(top, #fdbc40 0%, #ee9d02 100%); + background-image: linear-gradient(to bottom, #fdbc40 0%, #ee9d02 100%); +} + +.btn .icon { + float: left; + width: 14px; + height: 14px; + margin-top: 1px; + margin-bottom: 1px; + color: #737475; + font-size: 14px; + line-height: 1; +} + +.btn .icon-text { + margin-right: 5px; +} + +.btn-dropdown:after { + font-family: "photon-entypo"; + margin-left: 5px; + content: ""; +} + +.btn-group { + position: relative; + display: inline-block; + vertical-align: middle; + -webkit-app-region: no-drag; +} +.btn-group .btn { + position: relative; + float: left; +} +.btn-group .btn:focus, .btn-group .btn:active { + z-index: 2; +} +.btn-group .btn.active { + z-index: 3; +} + +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-group > .btn:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group .btn + .btn { + border-left: 1px solid #c2c0c2; +} +.btn-group .btn + .btn.active { + border-left: 0; +} +.btn-group .active { + color: #fff; + border: 1px solid transparent; + background-color: #6d6c6d; + background-image: none; +} +.btn-group .active .icon { + color: #fff; +} + +.toolbar { + min-height: 22px; + box-shadow: inset 0 1px 0 #f5f4f5; + background-color: #e8e6e8; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e8e6e8), color-stop(100%, #d1cfd1)); + background-image: -webkit-linear-gradient(top, #e8e6e8 0%, #d1cfd1 100%); + background-image: linear-gradient(to bottom, #e8e6e8 0%, #d1cfd1 100%); +} +.toolbar:before, .toolbar:after { + display: table; + content: " "; +} +.toolbar:after { + clear: both; +} + +.toolbar-header { + border-bottom: 1px solid #c2c0c2; +} +.toolbar-header .title { + margin-top: 1px; +} + +.toolbar-footer { + border-top: 1px solid #c2c0c2; + -webkit-app-region: drag; +} + +.title { + margin: 0; + font-size: 12px; + font-weight: 400; + text-align: center; + color: #555; + cursor: default; +} + +.toolbar-borderless { + border-top: 0; + border-bottom: 0; +} + +.toolbar-actions { + margin-top: 4px; + margin-bottom: 3px; + padding-right: 3px; + padding-left: 3px; + padding-bottom: 3px; + -webkit-app-region: drag; +} +.toolbar-actions:before, .toolbar-actions:after { + display: table; + content: " "; +} +.toolbar-actions:after { + clear: both; +} +.toolbar-actions > .btn, +.toolbar-actions > .btn-group { + margin-left: 4px; + margin-right: 4px; +} + +label { + display: inline-block; + font-size: 13px; + margin-bottom: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +input[type="search"] { + box-sizing: border-box; +} + +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + line-height: normal; +} + +.form-control { + display: inline-block; + width: 100%; + min-height: 25px; + padding: 5px 10px; + font-size: 13px; + line-height: 1.6; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + outline: none; +} +.form-control:focus { + border-color: #6db3fd; + box-shadow: 3px 3px 0 #6db3fd, -3px -3px 0 #6db3fd, -3px 3px 0 #6db3fd, 3px -3px 0 #6db3fd; +} + +textarea { + height: auto; +} + +.form-group { + margin-bottom: 10px; +} + +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; +} + +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-left: -20px; + margin-top: 4px; +} + +.form-actions .btn { + margin-right: 10px; +} +.form-actions .btn:last-child { + margin-right: 0; +} + +.pane-group { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: flex; +} + +.pane { + position: relative; + overflow-y: auto; + flex: 1; + border-left: 1px solid #ddd; +} +.pane:first-child { + border-left: 0; +} + +.pane-sm { + max-width: 220px; + min-width: 150px; +} + +.pane-mini { + width: 80px; + flex: none; +} + +.pane-one-fourth { + width: 25%; + flex: none; +} + +.pane-one-third { + width: 33.3%; +} + +img { + -webkit-user-drag: text; +} + +.img-circle { + border-radius: 50%; +} + +.img-rounded { + border-radius: 4px; +} + +.list-group { + width: 100%; + list-style: none; + margin: 0; + padding: 0; +} +.list-group * { + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.list-group-item { + padding: 10px; + font-size: 12px; + color: #414142; + border-top: 1px solid #ddd; +} +.list-group-item:first-child { + border-top: 0; +} +.list-group-item.active, .list-group-item.selected { + color: #fff; + background-color: #116cd6; +} + +.list-group-header { + padding: 10px; +} + +.media-object { + margin-top: 3px; +} + +.media-object.pull-left { + margin-right: 10px; +} + +.media-object.pull-right { + margin-left: 10px; +} + +.media-body { + overflow: hidden; +} + +.nav-group { + font-size: 14px; +} + +.nav-group-item { + padding: 2px 10px 2px 25px; + display: block; + color: #333; + text-decoration: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.nav-group-item:active, .nav-group-item.active { + background-color: #dcdfe1; +} +.nav-group-item .icon { + width: 19px; + height: 18px; + float: left; + color: #737475; + margin-top: -3px; + margin-right: 7px; + font-size: 18px; + text-align: center; +} + +.nav-group-title { + margin: 0; + padding: 10px 10px 2px; + font-size: 12px; + font-weight: 500; + color: #666666; +} + +@font-face { + font-family: "photon-entypo"; + src: url("../fonts/photon-entypo.eot"); + src: url("../fonts/photon-entypo.eot?#iefix") format("eot"), url("../fonts/photon-entypo.woff") format("woff"), url("../fonts/photon-entypo.ttf") format("truetype"); + font-weight: normal; + font-style: normal; +} +.icon:before { + position: relative; + display: inline-block; + font-family: "photon-entypo"; + speak: none; + font-size: 100%; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-note:before { + content: '\e800'; +} + +/* '' */ +.icon-note-beamed:before { + content: '\e801'; +} + +/* '' */ +.icon-music:before { + content: '\e802'; +} + +/* '' */ +.icon-search:before { + content: '\e803'; +} + +/* '' */ +.icon-flashlight:before { + content: '\e804'; +} + +/* '' */ +.icon-mail:before { + content: '\e805'; +} + +/* '' */ +.icon-heart:before { + content: '\e806'; +} + +/* '' */ +.icon-heart-empty:before { + content: '\e807'; +} + +/* '' */ +.icon-star:before { + content: '\e808'; +} + +/* '' */ +.icon-star-empty:before { + content: '\e809'; +} + +/* '' */ +.icon-user:before { + content: '\e80a'; +} + +/* '' */ +.icon-users:before { + content: '\e80b'; +} + +/* '' */ +.icon-user-add:before { + content: '\e80c'; +} + +/* '' */ +.icon-video:before { + content: '\e80d'; +} + +/* '' */ +.icon-picture:before { + content: '\e80e'; +} + +/* '' */ +.icon-camera:before { + content: '\e80f'; +} + +/* '' */ +.icon-layout:before { + content: '\e810'; +} + +/* '' */ +.icon-menu:before { + content: '\e811'; +} + +/* '' */ +.icon-check:before { + content: '\e812'; +} + +/* '' */ +.icon-cancel:before { + content: '\e813'; +} + +/* '' */ +.icon-cancel-circled:before { + content: '\e814'; +} + +/* '' */ +.icon-cancel-squared:before { + content: '\e815'; +} + +/* '' */ +.icon-plus:before { + content: '\e816'; +} + +/* '' */ +.icon-plus-circled:before { + content: '\e817'; +} + +/* '' */ +.icon-plus-squared:before { + content: '\e818'; +} + +/* '' */ +.icon-minus:before { + content: '\e819'; +} + +/* '' */ +.icon-minus-circled:before { + content: '\e81a'; +} + +/* '' */ +.icon-minus-squared:before { + content: '\e81b'; +} + +/* '' */ +.icon-help:before { + content: '\e81c'; +} + +/* '' */ +.icon-help-circled:before { + content: '\e81d'; +} + +/* '' */ +.icon-info:before { + content: '\e81e'; +} + +/* '' */ +.icon-info-circled:before { + content: '\e81f'; +} + +/* '' */ +.icon-back:before { + content: '\e820'; +} + +/* '' */ +.icon-home:before { + content: '\e821'; +} + +/* '' */ +.icon-link:before { + content: '\e822'; +} + +/* '' */ +.icon-attach:before { + content: '\e823'; +} + +/* '' */ +.icon-lock:before { + content: '\e824'; +} + +/* '' */ +.icon-lock-open:before { + content: '\e825'; +} + +/* '' */ +.icon-eye:before { + content: '\e826'; +} + +/* '' */ +.icon-tag:before { + content: '\e827'; +} + +/* '' */ +.icon-bookmark:before { + content: '\e828'; +} + +/* '' */ +.icon-bookmarks:before { + content: '\e829'; +} + +/* '' */ +.icon-flag:before { + content: '\e82a'; +} + +/* '' */ +.icon-thumbs-up:before { + content: '\e82b'; +} + +/* '' */ +.icon-thumbs-down:before { + content: '\e82c'; +} + +/* '' */ +.icon-download:before { + content: '\e82d'; +} + +/* '' */ +.icon-upload:before { + content: '\e82e'; +} + +/* '' */ +.icon-upload-cloud:before { + content: '\e82f'; +} + +/* '' */ +.icon-reply:before { + content: '\e830'; +} + +/* '' */ +.icon-reply-all:before { + content: '\e831'; +} + +/* '' */ +.icon-forward:before { + content: '\e832'; +} + +/* '' */ +.icon-quote:before { + content: '\e833'; +} + +/* '' */ +.icon-code:before { + content: '\e834'; +} + +/* '' */ +.icon-export:before { + content: '\e835'; +} + +/* '' */ +.icon-pencil:before { + content: '\e836'; +} + +/* '' */ +.icon-feather:before { + content: '\e837'; +} + +/* '' */ +.icon-print:before { + content: '\e838'; +} + +/* '' */ +.icon-retweet:before { + content: '\e839'; +} + +/* '' */ +.icon-keyboard:before { + content: '\e83a'; +} + +/* '' */ +.icon-comment:before { + content: '\e83b'; +} + +/* '' */ +.icon-chat:before { + content: '\e83c'; +} + +/* '' */ +.icon-bell:before { + content: '\e83d'; +} + +/* '' */ +.icon-attention:before { + content: '\e83e'; +} + +/* '' */ +.icon-alert:before { + content: '\e83f'; +} + +/* '' */ +.icon-vcard:before { + content: '\e840'; +} + +/* '' */ +.icon-address:before { + content: '\e841'; +} + +/* '' */ +.icon-location:before { + content: '\e842'; +} + +/* '' */ +.icon-map:before { + content: '\e843'; +} + +/* '' */ +.icon-direction:before { + content: '\e844'; +} + +/* '' */ +.icon-compass:before { + content: '\e845'; +} + +/* '' */ +.icon-cup:before { + content: '\e846'; +} + +/* '' */ +.icon-trash:before { + content: '\e847'; +} + +/* '' */ +.icon-doc:before { + content: '\e848'; +} + +/* '' */ +.icon-docs:before { + content: '\e849'; +} + +/* '' */ +.icon-doc-landscape:before { + content: '\e84a'; +} + +/* '' */ +.icon-doc-text:before { + content: '\e84b'; +} + +/* '' */ +.icon-doc-text-inv:before { + content: '\e84c'; +} + +/* '' */ +.icon-newspaper:before { + content: '\e84d'; +} + +/* '' */ +.icon-book-open:before { + content: '\e84e'; +} + +/* '' */ +.icon-book:before { + content: '\e84f'; +} + +/* '' */ +.icon-folder:before { + content: '\e850'; +} + +/* '' */ +.icon-archive:before { + content: '\e851'; +} + +/* '' */ +.icon-box:before { + content: '\e852'; +} + +/* '' */ +.icon-rss:before { + content: '\e853'; +} + +/* '' */ +.icon-phone:before { + content: '\e854'; +} + +/* '' */ +.icon-cog:before { + content: '\e855'; +} + +/* '' */ +.icon-tools:before { + content: '\e856'; +} + +/* '' */ +.icon-share:before { + content: '\e857'; +} + +/* '' */ +.icon-shareable:before { + content: '\e858'; +} + +/* '' */ +.icon-basket:before { + content: '\e859'; +} + +/* '' */ +.icon-bag:before { + content: '\e85a'; +} + +/* '' */ +.icon-calendar:before { + content: '\e85b'; +} + +/* '' */ +.icon-login:before { + content: '\e85c'; +} + +/* '' */ +.icon-logout:before { + content: '\e85d'; +} + +/* '' */ +.icon-mic:before { + content: '\e85e'; +} + +/* '' */ +.icon-mute:before { + content: '\e85f'; +} + +/* '' */ +.icon-sound:before { + content: '\e860'; +} + +/* '' */ +.icon-volume:before { + content: '\e861'; +} + +/* '' */ +.icon-clock:before { + content: '\e862'; +} + +/* '' */ +.icon-hourglass:before { + content: '\e863'; +} + +/* '' */ +.icon-lamp:before { + content: '\e864'; +} + +/* '' */ +.icon-light-down:before { + content: '\e865'; +} + +/* '' */ +.icon-light-up:before { + content: '\e866'; +} + +/* '' */ +.icon-adjust:before { + content: '\e867'; +} + +/* '' */ +.icon-block:before { + content: '\e868'; +} + +/* '' */ +.icon-resize-full:before { + content: '\e869'; +} + +/* '' */ +.icon-resize-small:before { + content: '\e86a'; +} + +/* '' */ +.icon-popup:before { + content: '\e86b'; +} + +/* '' */ +.icon-publish:before { + content: '\e86c'; +} + +/* '' */ +.icon-window:before { + content: '\e86d'; +} + +/* '' */ +.icon-arrow-combo:before { + content: '\e86e'; +} + +/* '' */ +.icon-down-circled:before { + content: '\e86f'; +} + +/* '' */ +.icon-left-circled:before { + content: '\e870'; +} + +/* '' */ +.icon-right-circled:before { + content: '\e871'; +} + +/* '' */ +.icon-up-circled:before { + content: '\e872'; +} + +/* '' */ +.icon-down-open:before { + content: '\e873'; +} + +/* '' */ +.icon-left-open:before { + content: '\e874'; +} + +/* '' */ +.icon-right-open:before { + content: '\e875'; +} + +/* '' */ +.icon-up-open:before { + content: '\e876'; +} + +/* '' */ +.icon-down-open-mini:before { + content: '\e877'; +} + +/* '' */ +.icon-left-open-mini:before { + content: '\e878'; +} + +/* '' */ +.icon-right-open-mini:before { + content: '\e879'; +} + +/* '' */ +.icon-up-open-mini:before { + content: '\e87a'; +} + +/* '' */ +.icon-down-open-big:before { + content: '\e87b'; +} + +/* '' */ +.icon-left-open-big:before { + content: '\e87c'; +} + +/* '' */ +.icon-right-open-big:before { + content: '\e87d'; +} + +/* '' */ +.icon-up-open-big:before { + content: '\e87e'; +} + +/* '' */ +.icon-down:before { + content: '\e87f'; +} + +/* '' */ +.icon-left:before { + content: '\e880'; +} + +/* '' */ +.icon-right:before { + content: '\e881'; +} + +/* '' */ +.icon-up:before { + content: '\e882'; +} + +/* '' */ +.icon-down-dir:before { + content: '\e883'; +} + +/* '' */ +.icon-left-dir:before { + content: '\e884'; +} + +/* '' */ +.icon-right-dir:before { + content: '\e885'; +} + +/* '' */ +.icon-up-dir:before { + content: '\e886'; +} + +/* '' */ +.icon-down-bold:before { + content: '\e887'; +} + +/* '' */ +.icon-left-bold:before { + content: '\e888'; +} + +/* '' */ +.icon-right-bold:before { + content: '\e889'; +} + +/* '' */ +.icon-up-bold:before { + content: '\e88a'; +} + +/* '' */ +.icon-down-thin:before { + content: '\e88b'; +} + +/* '' */ +.icon-left-thin:before { + content: '\e88c'; +} + +/* '' */ +.icon-right-thin:before { + content: '\e88d'; +} + +/* '' */ +.icon-up-thin:before { + content: '\e88e'; +} + +/* '' */ +.icon-ccw:before { + content: '\e88f'; +} + +/* '' */ +.icon-cw:before { + content: '\e890'; +} + +/* '' */ +.icon-arrows-ccw:before { + content: '\e891'; +} + +/* '' */ +.icon-level-down:before { + content: '\e892'; +} + +/* '' */ +.icon-level-up:before { + content: '\e893'; +} + +/* '' */ +.icon-shuffle:before { + content: '\e894'; +} + +/* '' */ +.icon-loop:before { + content: '\e895'; +} + +/* '' */ +.icon-switch:before { + content: '\e896'; +} + +/* '' */ +.icon-play:before { + content: '\e897'; +} + +/* '' */ +.icon-stop:before { + content: '\e898'; +} + +/* '' */ +.icon-pause:before { + content: '\e899'; +} + +/* '' */ +.icon-record:before { + content: '\e89a'; +} + +/* '' */ +.icon-to-end:before { + content: '\e89b'; +} + +/* '' */ +.icon-to-start:before { + content: '\e89c'; +} + +/* '' */ +.icon-fast-forward:before { + content: '\e89d'; +} + +/* '' */ +.icon-fast-backward:before { + content: '\e89e'; +} + +/* '' */ +.icon-progress-0:before { + content: '\e89f'; +} + +/* '' */ +.icon-progress-1:before { + content: '\e8a0'; +} + +/* '' */ +.icon-progress-2:before { + content: '\e8a1'; +} + +/* '' */ +.icon-progress-3:before { + content: '\e8a2'; +} + +/* '' */ +.icon-target:before { + content: '\e8a3'; +} + +/* '' */ +.icon-palette:before { + content: '\e8a4'; +} + +/* '' */ +.icon-list:before { + content: '\e8a5'; +} + +/* '' */ +.icon-list-add:before { + content: '\e8a6'; +} + +/* '' */ +.icon-signal:before { + content: '\e8a7'; +} + +/* '' */ +.icon-trophy:before { + content: '\e8a8'; +} + +/* '' */ +.icon-battery:before { + content: '\e8a9'; +} + +/* '' */ +.icon-back-in-time:before { + content: '\e8aa'; +} + +/* '' */ +.icon-monitor:before { + content: '\e8ab'; +} + +/* '' */ +.icon-mobile:before { + content: '\e8ac'; +} + +/* '' */ +.icon-network:before { + content: '\e8ad'; +} + +/* '' */ +.icon-cd:before { + content: '\e8ae'; +} + +/* '' */ +.icon-inbox:before { + content: '\e8af'; +} + +/* '' */ +.icon-install:before { + content: '\e8b0'; +} + +/* '' */ +.icon-globe:before { + content: '\e8b1'; +} + +/* '' */ +.icon-cloud:before { + content: '\e8b2'; +} + +/* '' */ +.icon-cloud-thunder:before { + content: '\e8b3'; +} + +/* '' */ +.icon-flash:before { + content: '\e8b4'; +} + +/* '' */ +.icon-moon:before { + content: '\e8b5'; +} + +/* '' */ +.icon-flight:before { + content: '\e8b6'; +} + +/* '' */ +.icon-paper-plane:before { + content: '\e8b7'; +} + +/* '' */ +.icon-leaf:before { + content: '\e8b8'; +} + +/* '' */ +.icon-lifebuoy:before { + content: '\e8b9'; +} + +/* '' */ +.icon-mouse:before { + content: '\e8ba'; +} + +/* '' */ +.icon-briefcase:before { + content: '\e8bb'; +} + +/* '' */ +.icon-suitcase:before { + content: '\e8bc'; +} + +/* '' */ +.icon-dot:before { + content: '\e8bd'; +} + +/* '' */ +.icon-dot-2:before { + content: '\e8be'; +} + +/* '' */ +.icon-dot-3:before { + content: '\e8bf'; +} + +/* '' */ +.icon-brush:before { + content: '\e8c0'; +} + +/* '' */ +.icon-magnet:before { + content: '\e8c1'; +} + +/* '' */ +.icon-infinity:before { + content: '\e8c2'; +} + +/* '' */ +.icon-erase:before { + content: '\e8c3'; +} + +/* '' */ +.icon-chart-pie:before { + content: '\e8c4'; +} + +/* '' */ +.icon-chart-line:before { + content: '\e8c5'; +} + +/* '' */ +.icon-chart-bar:before { + content: '\e8c6'; +} + +/* '' */ +.icon-chart-area:before { + content: '\e8c7'; +} + +/* '' */ +.icon-tape:before { + content: '\e8c8'; +} + +/* '' */ +.icon-graduation-cap:before { + content: '\e8c9'; +} + +/* '' */ +.icon-language:before { + content: '\e8ca'; +} + +/* '' */ +.icon-ticket:before { + content: '\e8cb'; +} + +/* '' */ +.icon-water:before { + content: '\e8cc'; +} + +/* '' */ +.icon-droplet:before { + content: '\e8cd'; +} + +/* '' */ +.icon-air:before { + content: '\e8ce'; +} + +/* '' */ +.icon-credit-card:before { + content: '\e8cf'; +} + +/* '' */ +.icon-floppy:before { + content: '\e8d0'; +} + +/* '' */ +.icon-clipboard:before { + content: '\e8d1'; +} + +/* '' */ +.icon-megaphone:before { + content: '\e8d2'; +} + +/* '' */ +.icon-database:before { + content: '\e8d3'; +} + +/* '' */ +.icon-drive:before { + content: '\e8d4'; +} + +/* '' */ +.icon-bucket:before { + content: '\e8d5'; +} + +/* '' */ +.icon-thermometer:before { + content: '\e8d6'; +} + +/* '' */ +.icon-key:before { + content: '\e8d7'; +} + +/* '' */ +.icon-flow-cascade:before { + content: '\e8d8'; +} + +/* '' */ +.icon-flow-branch:before { + content: '\e8d9'; +} + +/* '' */ +.icon-flow-tree:before { + content: '\e8da'; +} + +/* '' */ +.icon-flow-line:before { + content: '\e8db'; +} + +/* '' */ +.icon-flow-parallel:before { + content: '\e8dc'; +} + +/* '' */ +.icon-rocket:before { + content: '\e8dd'; +} + +/* '' */ +.icon-gauge:before { + content: '\e8de'; +} + +/* '' */ +.icon-traffic-cone:before { + content: '\e8df'; +} + +/* '' */ +.icon-cc:before { + content: '\e8e0'; +} + +/* '' */ +.icon-cc-by:before { + content: '\e8e1'; +} + +/* '' */ +.icon-cc-nc:before { + content: '\e8e2'; +} + +/* '' */ +.icon-cc-nc-eu:before { + content: '\e8e3'; +} + +/* '' */ +.icon-cc-nc-jp:before { + content: '\e8e4'; +} + +/* '' */ +.icon-cc-sa:before { + content: '\e8e5'; +} + +/* '' */ +.icon-cc-nd:before { + content: '\e8e6'; +} + +/* '' */ +.icon-cc-pd:before { + content: '\e8e7'; +} + +/* '' */ +.icon-cc-zero:before { + content: '\e8e8'; +} + +/* '' */ +.icon-cc-share:before { + content: '\e8e9'; +} + +/* '' */ +.icon-cc-remix:before { + content: '\e8ea'; +} + +/* '' */ +.icon-github:before { + content: '\e8eb'; +} + +/* '' */ +.icon-github-circled:before { + content: '\e8ec'; +} + +/* '' */ +.icon-flickr:before { + content: '\e8ed'; +} + +/* '' */ +.icon-flickr-circled:before { + content: '\e8ee'; +} + +/* '' */ +.icon-vimeo:before { + content: '\e8ef'; +} + +/* '' */ +.icon-vimeo-circled:before { + content: '\e8f0'; +} + +/* '' */ +.icon-twitter:before { + content: '\e8f1'; +} + +/* '' */ +.icon-twitter-circled:before { + content: '\e8f2'; +} + +/* '' */ +.icon-facebook:before { + content: '\e8f3'; +} + +/* '' */ +.icon-facebook-circled:before { + content: '\e8f4'; +} + +/* '' */ +.icon-facebook-squared:before { + content: '\e8f5'; +} + +/* '' */ +.icon-gplus:before { + content: '\e8f6'; +} + +/* '' */ +.icon-gplus-circled:before { + content: '\e8f7'; +} + +/* '' */ +.icon-pinterest:before { + content: '\e8f8'; +} + +/* '' */ +.icon-pinterest-circled:before { + content: '\e8f9'; +} + +/* '' */ +.icon-tumblr:before { + content: '\e8fa'; +} + +/* '' */ +.icon-tumblr-circled:before { + content: '\e8fb'; +} + +/* '' */ +.icon-linkedin:before { + content: '\e8fc'; +} + +/* '' */ +.icon-linkedin-circled:before { + content: '\e8fd'; +} + +/* '' */ +.icon-dribbble:before { + content: '\e8fe'; +} + +/* '' */ +.icon-dribbble-circled:before { + content: '\e8ff'; +} + +/* '' */ +.icon-stumbleupon:before { + content: '\e900'; +} + +/* '' */ +.icon-stumbleupon-circled:before { + content: '\e901'; +} + +/* '' */ +.icon-lastfm:before { + content: '\e902'; +} + +/* '' */ +.icon-lastfm-circled:before { + content: '\e903'; +} + +/* '' */ +.icon-rdio:before { + content: '\e904'; +} + +/* '' */ +.icon-rdio-circled:before { + content: '\e905'; +} + +/* '' */ +.icon-spotify:before { + content: '\e906'; +} + +/* '' */ +.icon-spotify-circled:before { + content: '\e907'; +} + +/* '' */ +.icon-qq:before { + content: '\e908'; +} + +/* '' */ +.icon-instagram:before { + content: '\e909'; +} + +/* '' */ +.icon-dropbox:before { + content: '\e90a'; +} + +/* '' */ +.icon-evernote:before { + content: '\e90b'; +} + +/* '' */ +.icon-flattr:before { + content: '\e90c'; +} + +/* '' */ +.icon-skype:before { + content: '\e90d'; +} + +/* '' */ +.icon-skype-circled:before { + content: '\e90e'; +} + +/* '' */ +.icon-renren:before { + content: '\e90f'; +} + +/* '' */ +.icon-sina-weibo:before { + content: '\e910'; +} + +/* '' */ +.icon-paypal:before { + content: '\e911'; +} + +/* '' */ +.icon-picasa:before { + content: '\e912'; +} + +/* '' */ +.icon-soundcloud:before { + content: '\e913'; +} + +/* '' */ +.icon-mixi:before { + content: '\e914'; +} + +/* '' */ +.icon-behance:before { + content: '\e915'; +} + +/* '' */ +.icon-google-circles:before { + content: '\e916'; +} + +/* '' */ +.icon-vkontakte:before { + content: '\e917'; +} + +/* '' */ +.icon-smashing:before { + content: '\e918'; +} + +/* '' */ +.icon-sweden:before { + content: '\e919'; +} + +/* '' */ +.icon-db-shape:before { + content: '\e91a'; +} + +/* '' */ +.icon-logo-db:before { + content: '\e91b'; +} + +/* '' */ +table { + width: 100%; + border: 0; + border-collapse: separate; + font-size: 12px; + text-align: left; +} + +thead { + background-color: #f5f5f4; +} + +tbody { + background-color: #fff; +} + +.table-striped tr:nth-child(even) { + background-color: #f5f5f4; +} + +tr:active, +.table-striped tr:active:nth-child(even) { + color: #fff; + background-color: #116cd6; +} + +thead tr:active { + color: #333; + background-color: #f5f5f4; +} + +th { + font-weight: normal; + border-right: 1px solid #ddd; + border-bottom: 1px solid #ddd; +} + +th, +td { + padding: 2px 15px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +th:last-child, +td:last-child { + border-right: 0; +} + +.tab-group { + margin-top: -1px; + display: flex; + border-top: 1px solid #989698; + border-bottom: 1px solid #989698; +} + +.tab-item { + position: relative; + flex: 1; + padding: 3px; + font-size: 12px; + text-align: center; + border-left: 1px solid #989698; + background-color: #b8b6b8; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #b8b6b8), color-stop(100%, #b0aeb0)); + background-image: -webkit-linear-gradient(top, #b8b6b8 0%, #b0aeb0 100%); + background-image: linear-gradient(to bottom, #b8b6b8 0%, #b0aeb0 100%); +} +.tab-item:first-child { + border-left: 0; +} +.tab-item.active { + background-color: #d4d2d4; + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #d4d2d4), color-stop(100%, #cccacc)); + background-image: -webkit-linear-gradient(top, #d4d2d4 0%, #cccacc 100%); + background-image: linear-gradient(to bottom, #d4d2d4 0%, #cccacc 100%); +} +.tab-item .icon-close-tab { + position: absolute; + top: 50%; + left: 5px; + width: 15px; + height: 15px; + font-size: 15px; + line-height: 15px; + text-align: center; + color: #666; + opacity: 0; + transition: opacity .1s linear, background-color .1s linear; + border-radius: 3px; + transform: translateY(-50%); + z-index: 10; +} +.tab-item:after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + content: ""; + background-color: rgba(0, 0, 0, 0.08); + opacity: 0; + transition: opacity .1s linear; + z-index: 1; +} +.tab-item:hover:not(.active):after { + opacity: 1; +} +.tab-item:hover .icon-close-tab { + opacity: 1; +} +.tab-item .icon-close-tab:hover { + background-color: rgba(0, 0, 0, 0.08); +} + +.tab-item-fixed { + flex: none; + padding: 3px 10px; +} diff --git a/Dashboard/content/css/photon.min.css b/Dashboard/content/css/photon.min.css new file mode 100644 index 00000000..35497345 --- /dev/null +++ b/Dashboard/content/css/photon.min.css @@ -0,0 +1,9 @@ +@charset "UTF-8";/*! + * ===================================================== + * Photon v0.1.1 + * Copyright 2015 Connor Sears + * Licensed under MIT (https://github.com/connors/proton/blob/master/LICENSE) + * + * v0.1.1 designed by @connors. + * ===================================================== + */audio,canvas,progress,sub,sup,video{vertical-align:baseline}body,html{height:100%}hr,html,label{overflow:hidden}.clearfix:after,.toolbar-actions:after,.toolbar:after{clear:both}*,img{-webkit-user-drag:text}.list-group *,.nav-group-item,h1,h2,h3,h4,h5,h6,label,td,th{white-space:nowrap;text-overflow:ellipsis}audio:not([controls]){display:none}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:36px}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sup{top:-.5em}.pane-group,.window{top:0;left:0;right:0}sub{bottom:-.25em}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}*{cursor:default;-webkit-user-select:none;-webkit-box-sizing:border-box;box-sizing:border-box}html{width:100%}body{padding:0;margin:0;font-family:system,-apple-system,".SFNSDisplay-Regular","Helvetica Neue",Helvetica,"Segoe UI",sans-serif;font-size:13px;line-height:1.6;color:#333;background-color:transparent}.btn-dropdown:after,.icon:before{font-family:photon-entypo}hr{margin:15px 0;background:0 0;border:0;border-bottom:1px solid #ddd}h1,h2,h3,h4,h5,h6{margin-top:20px;margin-bottom:10px;font-weight:500;overflow:hidden}.btn .icon,.toolbar-header .title{margin-top:1px}h2{font-size:30px}h3{font-size:24px}h4{font-size:18px}h5{font-size:14px}.btn,h6{font-size:12px}.window{position:absolute;bottom:0;display:flex;flex-direction:column;background-color:#fff}.window-content{position:relative;overflow-y:auto;display:flex;flex:1}.selectable-text{cursor:text;-webkit-user-select:text}.btn,.title{cursor:default}.text-center{text-align:center}.text-right{text-align:right}.text-left{text-align:left}.btn,.title{text-align:center}.pull-left{float:left}.pull-right{float:right}.padded{padding:10px}.padded-less{padding:5px}.padded-more{padding:20px}.padded-vertically{padding-top:10px;padding-bottom:10px}.padded-vertically-less{padding-top:5px;padding-bottom:5px}.padded-vertically-more{padding-top:20px;padding-bottom:20px}.padded-horizontally{padding-right:10px;padding-left:10px}.padded-horizontally-less{padding-right:5px;padding-left:5px}.padded-horizontally-more{padding-right:20px;padding-left:20px}.padded-top{padding-top:10px}.padded-top-less{padding-top:5px}.padded-top-more{padding-top:20px}.padded-bottom{padding-bottom:10px}.padded-bottom-less{padding-bottom:5px}.padded-bottom-more{padding-bottom:20px}.sidebar{background-color:#f5f5f4}.draggable{-webkit-app-region:drag}.btn,.btn-group{vertical-align:middle;-webkit-app-region:no-drag}.clearfix:after,.clearfix:before{display:table;content:" "}.btn{display:inline-block;padding:3px 8px;margin-bottom:0;line-height:1.4;white-space:nowrap;background-image:none;border:1px solid transparent;border-radius:4px;box-shadow:0 1px 1px rgba(0,0,0,.06)}.btn:focus{outline:0;box-shadow:none}.btn-mini{padding:2px 6px}.btn-large{padding:6px 12px}.btn-form{padding-right:20px;padding-left:20px}.btn-default{color:#333;background-color:#fcfcfc;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fcfcfc),color-stop(100%,#f1f1f1));background-image:-webkit-linear-gradient(top,#fcfcfc 0,#f1f1f1 100%);background-image:linear-gradient(to bottom,#fcfcfc 0,#f1f1f1 100%);border-color:#c2c0c2 #c2c0c2 #a19fa1}.btn-default:active{background-color:#ddd;background-image:none}.btn-negative,.btn-positive,.btn-primary,.btn-warning{color:#fff;text-shadow:0 1px 1px rgba(0,0,0,.1)}.btn-primary{border-color:#388df8 #388df8 #0866dc;background-color:#6eb4f7;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#6eb4f7),color-stop(100%,#1a82fb));background-image:-webkit-linear-gradient(top,#6eb4f7 0,#1a82fb 100%);background-image:linear-gradient(to bottom,#6eb4f7 0,#1a82fb 100%)}.btn-primary:active{background-color:#3e9bf4;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3e9bf4),color-stop(100%,#0469de));background-image:-webkit-linear-gradient(top,#3e9bf4 0,#0469de 100%);background-image:linear-gradient(to bottom,#3e9bf4 0,#0469de 100%)}.btn-positive{border-color:#29a03b #29a03b #248b34;background-color:#5bd46d;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#5bd46d),color-stop(100%,#29a03b));background-image:-webkit-linear-gradient(top,#5bd46d 0,#29a03b 100%);background-image:linear-gradient(to bottom,#5bd46d 0,#29a03b 100%)}.btn-positive:active{background-color:#34c84a;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#34c84a),color-stop(100%,#248b34));background-image:-webkit-linear-gradient(top,#34c84a 0,#248b34 100%);background-image:linear-gradient(to bottom,#34c84a 0,#248b34 100%)}.btn-negative{border-color:#fb2f29 #fb2f29 #fb1710;background-color:#fd918d;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fd918d),color-stop(100%,#fb2f29));background-image:-webkit-linear-gradient(top,#fd918d 0,#fb2f29 100%);background-image:linear-gradient(to bottom,#fd918d 0,#fb2f29 100%)}.btn-negative:active{background-color:#fc605b;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fc605b),color-stop(100%,#fb1710));background-image:-webkit-linear-gradient(top,#fc605b 0,#fb1710 100%);background-image:linear-gradient(to bottom,#fc605b 0,#fb1710 100%)}.btn-warning{border-color:#fcaa0e #fcaa0e #ee9d02;background-color:#fece72;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fece72),color-stop(100%,#fcaa0e));background-image:-webkit-linear-gradient(top,#fece72 0,#fcaa0e 100%);background-image:linear-gradient(to bottom,#fece72 0,#fcaa0e 100%)}.btn-warning:active{background-color:#fdbc40;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fdbc40),color-stop(100%,#ee9d02));background-image:-webkit-linear-gradient(top,#fdbc40 0,#ee9d02 100%);background-image:linear-gradient(to bottom,#fdbc40 0,#ee9d02 100%)}.btn .icon{float:left;width:14px;height:14px;margin-bottom:1px;color:#737475;font-size:14px;line-height:1}.btn .icon-text{margin-right:5px}.btn-dropdown:after{margin-left:5px;content:""}.btn-group{position:relative;display:inline-block}.toolbar-actions:after,.toolbar-actions:before,.toolbar:after,.toolbar:before{display:table;content:" "}.btn-group .btn{position:relative;float:left}.btn-group .btn:active,.btn-group .btn:focus{z-index:2}.btn-group .btn.active{z-index:3}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-group>.btn:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group .btn+.btn{border-left:1px solid #c2c0c2}.btn-group .btn+.btn.active{border-left:0}.btn-group .active{color:#fff;border:1px solid transparent;background-color:#6d6c6d;background-image:none}.btn-group .active .icon{color:#fff}.toolbar{min-height:22px;box-shadow:inset 0 1px 0 #f5f4f5;background-color:#e8e6e8;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#e8e6e8),color-stop(100%,#d1cfd1));background-image:-webkit-linear-gradient(top,#e8e6e8 0,#d1cfd1 100%);background-image:linear-gradient(to bottom,#e8e6e8 0,#d1cfd1 100%)}.toolbar-header{border-bottom:1px solid #c2c0c2}.toolbar-footer{border-top:1px solid #c2c0c2;-webkit-app-region:drag}.title{margin:0;font-size:12px;font-weight:400;color:#555}.toolbar-borderless{border-top:0;border-bottom:0}.toolbar-actions{margin-top:4px;margin-bottom:3px;padding-right:3px;padding-left:3px;padding-bottom:3px;-webkit-app-region:drag}.form-control,label{display:inline-block;font-size:13px}.toolbar-actions>.btn,.toolbar-actions>.btn-group{margin-left:4px;margin-right:4px}label{margin-bottom:5px}input[type=search]{-webkit-appearance:textfield;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;line-height:normal}.checkbox,.form-group,.radio{margin-bottom:10px}.form-control{width:100%;min-height:25px;padding:5px 10px;line-height:1.6;background-color:#fff;border:1px solid #ddd;border-radius:4px;outline:0}.form-control:focus{border-color:#6db3fd;box-shadow:3px 3px 0 #6db3fd,-3px -3px 0 #6db3fd,-3px 3px 0 #6db3fd,3px -3px 0 #6db3fd}textarea{height:auto}.checkbox,.radio{position:relative;display:block;margin-top:10px}.checkbox label,.radio label{padding-left:20px;margin-bottom:0;font-weight:400}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-left:-20px;margin-top:4px}.form-actions .btn{margin-right:10px}.form-actions .btn:last-child{margin-right:0}.pane-group{position:absolute;bottom:0;display:flex}.icon:before,.pane,.tab-item{position:relative}.pane{overflow-y:auto;flex:1;border-left:1px solid #ddd}.list-group *,.media-body,.nav-group-item,td,th{overflow:hidden}.pane:first-child{border-left:0}.pane-sm{max-width:220px;min-width:150px}.pane-mini{width:80px;flex:none}.pane-one-fourth{width:25%;flex:none}.pane-one-third{width:33.3%}.img-circle{border-radius:50%}.img-rounded{border-radius:4px}.list-group{width:100%;list-style:none;margin:0;padding:0}.list-group *{margin:0}.list-group-item{padding:10px;font-size:12px;color:#414142;border-top:1px solid #ddd}.list-group-item:first-child{border-top:0}.list-group-item.active,.list-group-item.selected{color:#fff;background-color:#116cd6}.list-group-header{padding:10px}.media-object{margin-top:3px}.media-object.pull-left{margin-right:10px}.media-object.pull-right{margin-left:10px}.nav-group{font-size:14px}.nav-group-item{padding:2px 10px 2px 25px;display:block;color:#333;text-decoration:none}.nav-group-item.active,.nav-group-item:active{background-color:#dcdfe1}.nav-group-item .icon{width:19px;height:18px;float:left;color:#737475;margin-top:-3px;margin-right:7px;font-size:18px;text-align:center}.nav-group-title{margin:0;padding:10px 10px 2px;font-size:12px;font-weight:500;color:#666}.icon:before,th{font-weight:400}@font-face{font-family:photon-entypo;src:url(../fonts/photon-entypo.eot);src:url(../fonts/photon-entypo.eot?#iefix) format("eot"),url(../fonts/photon-entypo.woff) format("woff"),url(../fonts/photon-entypo.ttf) format("truetype");font-weight:400;font-style:normal}.icon:before{display:inline-block;speak:none;font-size:100%;font-style:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-note:before{content:'\e800'}.icon-note-beamed:before{content:'\e801'}.icon-music:before{content:'\e802'}.icon-search:before{content:'\e803'}.icon-flashlight:before{content:'\e804'}.icon-mail:before{content:'\e805'}.icon-heart:before{content:'\e806'}.icon-heart-empty:before{content:'\e807'}.icon-star:before{content:'\e808'}.icon-star-empty:before{content:'\e809'}.icon-user:before{content:'\e80a'}.icon-users:before{content:'\e80b'}.icon-user-add:before{content:'\e80c'}.icon-video:before{content:'\e80d'}.icon-picture:before{content:'\e80e'}.icon-camera:before{content:'\e80f'}.icon-layout:before{content:'\e810'}.icon-menu:before{content:'\e811'}.icon-check:before{content:'\e812'}.icon-cancel:before{content:'\e813'}.icon-cancel-circled:before{content:'\e814'}.icon-cancel-squared:before{content:'\e815'}.icon-plus:before{content:'\e816'}.icon-plus-circled:before{content:'\e817'}.icon-plus-squared:before{content:'\e818'}.icon-minus:before{content:'\e819'}.icon-minus-circled:before{content:'\e81a'}.icon-minus-squared:before{content:'\e81b'}.icon-help:before{content:'\e81c'}.icon-help-circled:before{content:'\e81d'}.icon-info:before{content:'\e81e'}.icon-info-circled:before{content:'\e81f'}.icon-back:before{content:'\e820'}.icon-home:before{content:'\e821'}.icon-link:before{content:'\e822'}.icon-attach:before{content:'\e823'}.icon-lock:before{content:'\e824'}.icon-lock-open:before{content:'\e825'}.icon-eye:before{content:'\e826'}.icon-tag:before{content:'\e827'}.icon-bookmark:before{content:'\e828'}.icon-bookmarks:before{content:'\e829'}.icon-flag:before{content:'\e82a'}.icon-thumbs-up:before{content:'\e82b'}.icon-thumbs-down:before{content:'\e82c'}.icon-download:before{content:'\e82d'}.icon-upload:before{content:'\e82e'}.icon-upload-cloud:before{content:'\e82f'}.icon-reply:before{content:'\e830'}.icon-reply-all:before{content:'\e831'}.icon-forward:before{content:'\e832'}.icon-quote:before{content:'\e833'}.icon-code:before{content:'\e834'}.icon-export:before{content:'\e835'}.icon-pencil:before{content:'\e836'}.icon-feather:before{content:'\e837'}.icon-print:before{content:'\e838'}.icon-retweet:before{content:'\e839'}.icon-keyboard:before{content:'\e83a'}.icon-comment:before{content:'\e83b'}.icon-chat:before{content:'\e83c'}.icon-bell:before{content:'\e83d'}.icon-attention:before{content:'\e83e'}.icon-alert:before{content:'\e83f'}.icon-vcard:before{content:'\e840'}.icon-address:before{content:'\e841'}.icon-location:before{content:'\e842'}.icon-map:before{content:'\e843'}.icon-direction:before{content:'\e844'}.icon-compass:before{content:'\e845'}.icon-cup:before{content:'\e846'}.icon-trash:before{content:'\e847'}.icon-doc:before{content:'\e848'}.icon-docs:before{content:'\e849'}.icon-doc-landscape:before{content:'\e84a'}.icon-doc-text:before{content:'\e84b'}.icon-doc-text-inv:before{content:'\e84c'}.icon-newspaper:before{content:'\e84d'}.icon-book-open:before{content:'\e84e'}.icon-book:before{content:'\e84f'}.icon-folder:before{content:'\e850'}.icon-archive:before{content:'\e851'}.icon-box:before{content:'\e852'}.icon-rss:before{content:'\e853'}.icon-phone:before{content:'\e854'}.icon-cog:before{content:'\e855'}.icon-tools:before{content:'\e856'}.icon-share:before{content:'\e857'}.icon-shareable:before{content:'\e858'}.icon-basket:before{content:'\e859'}.icon-bag:before{content:'\e85a'}.icon-calendar:before{content:'\e85b'}.icon-login:before{content:'\e85c'}.icon-logout:before{content:'\e85d'}.icon-mic:before{content:'\e85e'}.icon-mute:before{content:'\e85f'}.icon-sound:before{content:'\e860'}.icon-volume:before{content:'\e861'}.icon-clock:before{content:'\e862'}.icon-hourglass:before{content:'\e863'}.icon-lamp:before{content:'\e864'}.icon-light-down:before{content:'\e865'}.icon-light-up:before{content:'\e866'}.icon-adjust:before{content:'\e867'}.icon-block:before{content:'\e868'}.icon-resize-full:before{content:'\e869'}.icon-resize-small:before{content:'\e86a'}.icon-popup:before{content:'\e86b'}.icon-publish:before{content:'\e86c'}.icon-window:before{content:'\e86d'}.icon-arrow-combo:before{content:'\e86e'}.icon-down-circled:before{content:'\e86f'}.icon-left-circled:before{content:'\e870'}.icon-right-circled:before{content:'\e871'}.icon-up-circled:before{content:'\e872'}.icon-down-open:before{content:'\e873'}.icon-left-open:before{content:'\e874'}.icon-right-open:before{content:'\e875'}.icon-up-open:before{content:'\e876'}.icon-down-open-mini:before{content:'\e877'}.icon-left-open-mini:before{content:'\e878'}.icon-right-open-mini:before{content:'\e879'}.icon-up-open-mini:before{content:'\e87a'}.icon-down-open-big:before{content:'\e87b'}.icon-left-open-big:before{content:'\e87c'}.icon-right-open-big:before{content:'\e87d'}.icon-up-open-big:before{content:'\e87e'}.icon-down:before{content:'\e87f'}.icon-left:before{content:'\e880'}.icon-right:before{content:'\e881'}.icon-up:before{content:'\e882'}.icon-down-dir:before{content:'\e883'}.icon-left-dir:before{content:'\e884'}.icon-right-dir:before{content:'\e885'}.icon-up-dir:before{content:'\e886'}.icon-down-bold:before{content:'\e887'}.icon-left-bold:before{content:'\e888'}.icon-right-bold:before{content:'\e889'}.icon-up-bold:before{content:'\e88a'}.icon-down-thin:before{content:'\e88b'}.icon-left-thin:before{content:'\e88c'}.icon-right-thin:before{content:'\e88d'}.icon-up-thin:before{content:'\e88e'}.icon-ccw:before{content:'\e88f'}.icon-cw:before{content:'\e890'}.icon-arrows-ccw:before{content:'\e891'}.icon-level-down:before{content:'\e892'}.icon-level-up:before{content:'\e893'}.icon-shuffle:before{content:'\e894'}.icon-loop:before{content:'\e895'}.icon-switch:before{content:'\e896'}.icon-play:before{content:'\e897'}.icon-stop:before{content:'\e898'}.icon-pause:before{content:'\e899'}.icon-record:before{content:'\e89a'}.icon-to-end:before{content:'\e89b'}.icon-to-start:before{content:'\e89c'}.icon-fast-forward:before{content:'\e89d'}.icon-fast-backward:before{content:'\e89e'}.icon-progress-0:before{content:'\e89f'}.icon-progress-1:before{content:'\e8a0'}.icon-progress-2:before{content:'\e8a1'}.icon-progress-3:before{content:'\e8a2'}.icon-target:before{content:'\e8a3'}.icon-palette:before{content:'\e8a4'}.icon-list:before{content:'\e8a5'}.icon-list-add:before{content:'\e8a6'}.icon-signal:before{content:'\e8a7'}.icon-trophy:before{content:'\e8a8'}.icon-battery:before{content:'\e8a9'}.icon-back-in-time:before{content:'\e8aa'}.icon-monitor:before{content:'\e8ab'}.icon-mobile:before{content:'\e8ac'}.icon-network:before{content:'\e8ad'}.icon-cd:before{content:'\e8ae'}.icon-inbox:before{content:'\e8af'}.icon-install:before{content:'\e8b0'}.icon-globe:before{content:'\e8b1'}.icon-cloud:before{content:'\e8b2'}.icon-cloud-thunder:before{content:'\e8b3'}.icon-flash:before{content:'\e8b4'}.icon-moon:before{content:'\e8b5'}.icon-flight:before{content:'\e8b6'}.icon-paper-plane:before{content:'\e8b7'}.icon-leaf:before{content:'\e8b8'}.icon-lifebuoy:before{content:'\e8b9'}.icon-mouse:before{content:'\e8ba'}.icon-briefcase:before{content:'\e8bb'}.icon-suitcase:before{content:'\e8bc'}.icon-dot:before{content:'\e8bd'}.icon-dot-2:before{content:'\e8be'}.icon-dot-3:before{content:'\e8bf'}.icon-brush:before{content:'\e8c0'}.icon-magnet:before{content:'\e8c1'}.icon-infinity:before{content:'\e8c2'}.icon-erase:before{content:'\e8c3'}.icon-chart-pie:before{content:'\e8c4'}.icon-chart-line:before{content:'\e8c5'}.icon-chart-bar:before{content:'\e8c6'}.icon-chart-area:before{content:'\e8c7'}.icon-tape:before{content:'\e8c8'}.icon-graduation-cap:before{content:'\e8c9'}.icon-language:before{content:'\e8ca'}.icon-ticket:before{content:'\e8cb'}.icon-water:before{content:'\e8cc'}.icon-droplet:before{content:'\e8cd'}.icon-air:before{content:'\e8ce'}.icon-credit-card:before{content:'\e8cf'}.icon-floppy:before{content:'\e8d0'}.icon-clipboard:before{content:'\e8d1'}.icon-megaphone:before{content:'\e8d2'}.icon-database:before{content:'\e8d3'}.icon-drive:before{content:'\e8d4'}.icon-bucket:before{content:'\e8d5'}.icon-thermometer:before{content:'\e8d6'}.icon-key:before{content:'\e8d7'}.icon-flow-cascade:before{content:'\e8d8'}.icon-flow-branch:before{content:'\e8d9'}.icon-flow-tree:before{content:'\e8da'}.icon-flow-line:before{content:'\e8db'}.icon-flow-parallel:before{content:'\e8dc'}.icon-rocket:before{content:'\e8dd'}.icon-gauge:before{content:'\e8de'}.icon-traffic-cone:before{content:'\e8df'}.icon-cc:before{content:'\e8e0'}.icon-cc-by:before{content:'\e8e1'}.icon-cc-nc:before{content:'\e8e2'}.icon-cc-nc-eu:before{content:'\e8e3'}.icon-cc-nc-jp:before{content:'\e8e4'}.icon-cc-sa:before{content:'\e8e5'}.icon-cc-nd:before{content:'\e8e6'}.icon-cc-pd:before{content:'\e8e7'}.icon-cc-zero:before{content:'\e8e8'}.icon-cc-share:before{content:'\e8e9'}.icon-cc-remix:before{content:'\e8ea'}.icon-github:before{content:'\e8eb'}.icon-github-circled:before{content:'\e8ec'}.icon-flickr:before{content:'\e8ed'}.icon-flickr-circled:before{content:'\e8ee'}.icon-vimeo:before{content:'\e8ef'}.icon-vimeo-circled:before{content:'\e8f0'}.icon-twitter:before{content:'\e8f1'}.icon-twitter-circled:before{content:'\e8f2'}.icon-facebook:before{content:'\e8f3'}.icon-facebook-circled:before{content:'\e8f4'}.icon-facebook-squared:before{content:'\e8f5'}.icon-gplus:before{content:'\e8f6'}.icon-gplus-circled:before{content:'\e8f7'}.icon-pinterest:before{content:'\e8f8'}.icon-pinterest-circled:before{content:'\e8f9'}.icon-tumblr:before{content:'\e8fa'}.icon-tumblr-circled:before{content:'\e8fb'}.icon-linkedin:before{content:'\e8fc'}.icon-linkedin-circled:before{content:'\e8fd'}.icon-dribbble:before{content:'\e8fe'}.icon-dribbble-circled:before{content:'\e8ff'}.icon-stumbleupon:before{content:'\e900'}.icon-stumbleupon-circled:before{content:'\e901'}.icon-lastfm:before{content:'\e902'}.icon-lastfm-circled:before{content:'\e903'}.icon-rdio:before{content:'\e904'}.icon-rdio-circled:before{content:'\e905'}.icon-spotify:before{content:'\e906'}.icon-spotify-circled:before{content:'\e907'}.icon-qq:before{content:'\e908'}.icon-instagram:before{content:'\e909'}.icon-dropbox:before{content:'\e90a'}.icon-evernote:before{content:'\e90b'}.icon-flattr:before{content:'\e90c'}.icon-skype:before{content:'\e90d'}.icon-skype-circled:before{content:'\e90e'}.icon-renren:before{content:'\e90f'}.icon-sina-weibo:before{content:'\e910'}.icon-paypal:before{content:'\e911'}.icon-picasa:before{content:'\e912'}.icon-soundcloud:before{content:'\e913'}.icon-mixi:before{content:'\e914'}.icon-behance:before{content:'\e915'}.icon-google-circles:before{content:'\e916'}.icon-vkontakte:before{content:'\e917'}.icon-smashing:before{content:'\e918'}.icon-sweden:before{content:'\e919'}.icon-db-shape:before{content:'\e91a'}.icon-logo-db:before{content:'\e91b'}table{border-spacing:0;width:100%;border:0;border-collapse:separate;font-size:12px;text-align:left}.table-striped tr:nth-child(even),thead{background-color:#f5f5f4}tbody{background-color:#fff}.table-striped tr:active:nth-child(even),tr:active{color:#fff;background-color:#116cd6}thead tr:active{color:#333;background-color:#f5f5f4}th{border-right:1px solid #ddd;border-bottom:1px solid #ddd}td,th{padding:2px 15px}td:last-child,th:last-child{border-right:0}.tab-group{margin-top:-1px;display:flex;border-top:1px solid #989698;border-bottom:1px solid #989698}.tab-item{flex:1;padding:3px;font-size:12px;text-align:center;border-left:1px solid #989698;background-color:#b8b6b8;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#b8b6b8),color-stop(100%,#b0aeb0));background-image:-webkit-linear-gradient(top,#b8b6b8 0,#b0aeb0 100%);background-image:linear-gradient(to bottom,#b8b6b8 0,#b0aeb0 100%)}.tab-item:first-child{border-left:0}.tab-item.active{background-color:#d4d2d4;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#d4d2d4),color-stop(100%,#cccacc));background-image:-webkit-linear-gradient(top,#d4d2d4 0,#cccacc 100%);background-image:linear-gradient(to bottom,#d4d2d4 0,#cccacc 100%)}.tab-item .icon-close-tab:hover,.tab-item:after{background-color:rgba(0,0,0,.08)}.tab-item .icon-close-tab{position:absolute;top:50%;left:5px;width:15px;height:15px;font-size:15px;line-height:15px;text-align:center;color:#666;opacity:0;transition:opacity .1s linear,background-color .1s linear;border-radius:3px;transform:translateY(-50%);z-index:10}.tab-item:after{position:absolute;top:0;right:0;bottom:0;left:0;content:"";opacity:0;transition:opacity .1s linear;z-index:1}.tab-item:hover .icon-close-tab,.tab-item:hover:not(.active):after{opacity:1}.tab-item-fixed{flex:none;padding:3px 10px} \ No newline at end of file diff --git a/Dashboard/content/fonts/photon-entypo.eot b/Dashboard/content/fonts/photon-entypo.eot new file mode 100644 index 0000000000000000000000000000000000000000..f70d1267468a1a3fe469fdaaf2e10e3079d210f4 GIT binary patch literal 48644 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 KUq|oIrTTvrfQ8cl literal 0 HcmV?d00001 diff --git a/Dashboard/content/fonts/photon-entypo.svg b/Dashboard/content/fonts/photon-entypo.svg new file mode 100644 index 00000000..9256ea8f --- /dev/null +++ b/Dashboard/content/fonts/photon-entypo.svg @@ -0,0 +1,295 @@ + + + +Copyright (C) 2015 by original authors @ fontello.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dashboard/content/fonts/photon-entypo.ttf b/Dashboard/content/fonts/photon-entypo.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7f765b348feb4157cf8d677d712e4d67183c549d GIT binary patch literal 48456 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 literal 0 HcmV?d00001 diff --git a/Dashboard/content/fonts/photon-entypo.woff b/Dashboard/content/fonts/photon-entypo.woff new file mode 100644 index 0000000000000000000000000000000000000000..87860e570c608d8287153ac669096b04d0df0645 GIT binary patch literal 30692 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 + + + ExpoSE Dashboard + + + + + + + + + + + +

+ +
+
+ +
+
+
+ + + + +
+
+
+ + + + + + + + + +
Metrics
+
+
+
+ + + + + + + + + + + +
File NameCoverage (%)Found BlocksTotal Blocks
+
+
+
+ +
+
+
+
+
+ + + + + + + + +
Output
+
+
+
+
+ + + + + + + + + + + +
ReplayTest CaseTimeError Count
+
+
+
+
+ + + + + + + + + + +
ReplayTest CaseError
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/Dashboard/content/js/graph.js b/Dashboard/content/js/graph.js new file mode 100644 index 00000000..d6b61723 --- /dev/null +++ b/Dashboard/content/js/graph.js @@ -0,0 +1,74 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +const output = require('./output'); +const remote = require('electron').remote; +const {dialog} = remote.require('electron'); +const tmp = remote.require('tmp'); + +let current; + +function Graph(summary) { + current = summary; + Graph.png(summary, tmp.fileSync().name); +} + +Graph.png = function(summary, pngFile) { + Graph.out(summary, 'png size 1024,600', pngFile); +} + +Graph.tex = function(summary, texFile) { + Graph.out(summary, 'epslatex', texFile); +} + +Graph.findFile = function(type) { + return dialog.showSaveDialog({properties: ['saveFile'], filters: type}); +} + +Graph.savePng = function() { + + if (!current) { + return; + } + + let file = Graph.findFile([{name: 'PNG', extensions: ['png']}]); + + if (!file) { + return; + } + + Graph.png(current, '' + file); +} + +Graph.saveTex = function() { + + if (!current) { + return; + } + + let file = Graph.findFile([{name: 'LaTex', extensions: ['tex']}]); + + if (!file) { + return; + } + + Graph.tex(current, '' + file); +} + +Graph.out = function(summary, mode, pngFile) { + let remote = require('electron').remote; + let GraphDataWriter = remote.require('../src/graph_data'); + let GraphBuilder = remote.require('../src/graph_builder'); + let covTmp = tmp.fileSync(); + let rateTmp = tmp.fileSync(); + let files = GraphDataWriter(summary, covTmp.name, rateTmp.name); + + GraphBuilder(pngFile, mode, files.coverage, files.rate, function() { + covTmp.removeCallback(); + rateTmp.removeCallback(); + $('#graph_content').html(''); + }); +} + +module.exports = Graph; diff --git a/Dashboard/content/js/jquery.js b/Dashboard/content/js/jquery.js new file mode 100644 index 00000000..4c5be4c0 --- /dev/null +++ b/Dashboard/content/js/jquery.js @@ -0,0 +1,4 @@ +/*! jQuery v3.1.1 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):C.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/[^\x20\t\r\n\f]+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R), +a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ka=/^$|\/(?:java|ecma)script/i,la={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};la.optgroup=la.option,la.tbody=la.tfoot=la.colgroup=la.caption=la.thead,la.th=la.td;function ma(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function na(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=ma(l.appendChild(f),"script"),j&&na(g),c){k=0;while(f=g[k++])ka.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var qa=d.documentElement,ra=/^key/,sa=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ta=/^([^.]*)(?:\.(.+)|)/;function ua(){return!0}function va(){return!1}function wa(){try{return d.activeElement}catch(a){}}function xa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)xa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=va;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(qa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,za=/\s*$/g;function Da(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Ea(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Fa(a){var b=Ba.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ga(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Aa.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ia(f,b,c,d)});if(m&&(e=pa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(ma(e,"script"),Ea),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=ma(h),f=ma(a),d=0,e=f.length;d0&&na(g,!i&&ma(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ja(this,a,!0)},remove:function(a){return Ja(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.appendChild(a)}})},prepend:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(ma(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!za.test(a)&&!la[(ja.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Ya(a,b,c,d,e){return new Ya.prototype.init(a,b,c,d,e)}r.Tween=Ya,Ya.prototype={constructor:Ya,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Ya.propHooks[this.prop];return a&&a.get?a.get(this):Ya.propHooks._default.get(this)},run:function(a){var b,c=Ya.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ya.propHooks._default.set(this),this}},Ya.prototype.init.prototype=Ya.prototype,Ya.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Ya.propHooks.scrollTop=Ya.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Ya.prototype.init,r.fx.step={};var Za,$a,_a=/^(?:toggle|show|hide)$/,ab=/queueHooks$/;function bb(){$a&&(a.requestAnimationFrame(bb),r.fx.tick())}function cb(){return a.setTimeout(function(){Za=void 0}),Za=r.now()}function db(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ba[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function eb(a,b,c){for(var d,e=(hb.tweeners[b]||[]).concat(hb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?ib:void 0)), +void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),ib={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=jb[b]||r.find.attr;jb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=jb[g],jb[g]=e,e=null!=c(a,b,d)?g:null,jb[g]=f),e}});var kb=/^(?:input|select|textarea|button)$/i,lb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):kb.test(a.nodeName)||lb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function mb(a){var b=a.match(K)||[];return b.join(" ")}function nb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,nb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,nb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,nb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=nb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(nb(c))+" ").indexOf(b)>-1)return!0;return!1}});var ob=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(ob,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:mb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ia.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,"$1"),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" + + + + + + +
+
+
+
+
+ + + + + + + + +
Output
+
+
+
+
+
+ + \ No newline at end of file diff --git a/Dashboard/package.json b/Dashboard/package.json new file mode 100644 index 00000000..4a5d4bd4 --- /dev/null +++ b/Dashboard/package.json @@ -0,0 +1,18 @@ +{ + "name": "expose-dashboard", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "author": "Blake Loring", + "scripts": { + "start": "electron ." + }, + "devDependencies": { + "electron": "^1.4.1" + }, + "dependencies": { + "electron": "^1.4.15", + "photon": "^2.0.0", + "tmp": "0.0.31" + } +} diff --git a/Dashboard/scripts/plot_data b/Dashboard/scripts/plot_data new file mode 100755 index 00000000..64863c85 --- /dev/null +++ b/Dashboard/scripts/plot_data @@ -0,0 +1,31 @@ +#!/bin/bash +echo "set term $7" +echo "set output '$3'" +echo "set grid" + +echo "set ylabel 'Coverage (\\%)'" +echo "set xlabel ''" +echo "set xrange [0:${5}]" +echo "set yrange [0:100]" +echo "set title '$4'" + +echo "set multiplot" + +echo "set size 1, 0.625" +echo "set origin 0.0,0.325" + +echo "plot'$1' w lines lw 2.5 lc 'red' title ''" + +echo "unset yrange" +echo "set size 1, 0.375" +echo "set origin 0.0,0.0" + +echo "set title ''" + +echo "set yrange [:${6}]" +echo "set ylabel 'Test / s'" +echo "set xlabel 'Time (s)'" +echo "plot '$2' w fillsteps fs solid 0.3 noborder lt 1 title '', '$2' with steps lt 1 lw 1 title ''" +echo "unset multiplot" + +echo "exit" \ No newline at end of file diff --git a/Dashboard/scripts/replot b/Dashboard/scripts/replot new file mode 100755 index 00000000..f3d4dad6 --- /dev/null +++ b/Dashboard/scripts/replot @@ -0,0 +1,3 @@ +#!/bin/bash +echo ${@:1} +./scripts/plot_data "${@:1}" | gnuplot \ No newline at end of file diff --git a/Dashboard/src/expose.js b/Dashboard/src/expose.js new file mode 100644 index 00000000..f8e80a57 --- /dev/null +++ b/Dashboard/src/expose.js @@ -0,0 +1,82 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +function toPercentage(aggregate) { + return (aggregate * 100).toFixed(2); +} + +function toSeconds(v) { + return (v / 1000 / 1000).toFixed(2) + 's'; +} + +function averageList(list) { + return list.reduce((last, cur) => last + cur.time, 0) / list.length; +} + +function coverage(job) { + job.coverage.forEach(x => { + x.percentage = toPercentage(x.data.found / x.data.total) + }); +} + +function internal(filename) { + return filename.indexOf('/Annotations/') != -1 || filename.indexOf('/S$/') != -1 || filename.indexOf('/ExpoSE/lib/') != -1; +} + +function aggregateCoverage(job) { + let coveredBlocks = 0; + let totalBlocks = 0; + + job.coverage.forEach(x => { + if (!internal(x.file)) { + coveredBlocks += x.data.found; + totalBlocks += x.data.total; + } + }); + + return toPercentage(coveredBlocks / totalBlocks); +} + +function sort(summary) { + summary.done.forEach(x => { + x.endTime = x.startTime + x.time; + }); + + summary.done.sort((left, right) => left.endTime - right.endTime); +} + +function min(list) { + return list.reduce((last, cur) => cur.time < last ? cur.time : last, list[0].time); +} + +function max(list) { + return list.reduce((last, cur) => cur.time > last ? cur.time : last, 0); +} + +function summaryInfo(summary) { + let jobList = summary.done; + let midPoint = jobList[Math.floor(jobList.length / 2)]; + + return { + pathCount: jobList.length, + totalExec: toSeconds(summary.end - summary.start), + meanTest: toSeconds(averageList(jobList)), + medianTest: toSeconds(midPoint.time), + worstCase: toSeconds(min(jobList)), + bestCase: toSeconds(max(jobList)) + } +} + +function buildErrors(summary) { + return summary.done.reduce((x, y) => x.concat(y.errors), []); +} + +module.exports = { + sort: sort, + buildErrors, + coverage: coverage, + aggregateCoverage: aggregateCoverage, + summaryInfo: summaryInfo, + OUT_REGEX: /ExpoSE JSON: ([\s\S]*)\nEND JSON/ +} diff --git a/Dashboard/src/expose_executor.js b/Dashboard/src/expose_executor.js new file mode 100644 index 00000000..94dec967 --- /dev/null +++ b/Dashboard/src/expose_executor.js @@ -0,0 +1,52 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +let spawn = require('child_process').spawn; + +const EXPOSE_PATH = 'expoSE'; + +//TODO: Executor should use summary.jobs[n].replay to build replay for consistency +//Otherwise 1 change becomes 2 +function Executor(filepath, input, data, done) { + + let env = process.env; + env.EXPOSE_JSON_OUT = 1; + + let args; + + if (input) { + args = ['replay', filepath, input]; + } else { + args = ['test', filepath]; + } + + let prc = spawn(EXPOSE_PATH, args, { + stdio: ['ignore', 'pipe', 'pipe'], + env: env, + disconnected: false + }); + + prc.final = ''; + prc.running = true; + + function record(d) { + this.final += d; + data(d); + } + + prc.stdout.on('data', function(d) { + this.final += d; + data(d); + }.bind(prc)); + prc.stderr.on('data', data); + + prc.stdout.on('close', function(c) { + this.running = false; + done(c); + }.bind(prc)); + + return prc; +} + +module.exports = Executor; diff --git a/Dashboard/src/graph_builder.js b/Dashboard/src/graph_builder.js new file mode 100644 index 00000000..4b4d9add --- /dev/null +++ b/Dashboard/src/graph_builder.js @@ -0,0 +1,24 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +let spawn = require('child_process').spawn; + +const REPLOTTER = './scripts/replot'; + +function BuildGraph(outFile, outType, inCoverage, inRate, done) { + let args = [inCoverage, inRate, outFile, "", "", "", outType]; + + console.log('Calling replot with ' + args); + + let prc = spawn(REPLOTTER, args, { + disconnected: false + }); + + prc.stdout.on('close', function(c) { + this.running = false; + done(c); + }.bind(this)); +} + +module.exports = BuildGraph; diff --git a/Dashboard/src/graph_data.js b/Dashboard/src/graph_data.js new file mode 100644 index 00000000..57e42d00 --- /dev/null +++ b/Dashboard/src/graph_data.js @@ -0,0 +1,63 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +const fs = require('fs'); +const expose = require('./expose'); +const TIMESTEP = 1000 * 1000; + +function toSeconds(v) { + return v / 1000 / 1000; +} + +function timeutil(start, x) { + return toSeconds(x - start.startTime); +} + +function averageList(list) { + return list.reduce((last, cur) => last + cur.time, 0) / list.length; +} + +function handlePercentage(summary, outFile) { + let jobList = summary.jobs; + + let time = timeutil.bind(this, jobList[0]); + + let coverageLines = "0, 0\n"; + + let lastCoverage = 0; + + jobList.forEach((x, i) => { + coverageLines += '' + time(x.endTime) + ', ' + Math.max(expose.aggregateCoverage(x), lastCoverage) + '\n'; + lastCoverage = expose.aggregateCoverage(x); + }); + + fs.writeFileSync(outFile, coverageLines); +} + +function handlePerSecond(summary, outFile) { + let jobList = summary.jobs; + let startTime = jobList[0].startTime; + let endTime = jobList[jobList.length - 1].endTime; + let time = timeutil.bind(this, jobList[0]); + let intervalLines = "0, 0\n"; + + for (let i = startTime; i < endTime; i += TIMESTEP) { + let jobsInInterval = jobList.filter(x => x.endTime >= i && x.endTime <= (i + TIMESTEP)).length; + intervalLines += '' + time(i) + ', ' + jobsInInterval + '\n'; + } + + fs.writeFileSync(outFile, intervalLines); +} + +function buildGraphData(summary, pcOutFile, rateOutFile) { + handlePercentage(summary, pcOutFile); + handlePerSecond(summary, rateOutFile); + + return { + coverage: pcOutFile, + rate: rateOutFile + } +} + +module.exports = buildGraphData; diff --git a/Dashboard/src/main.js b/Dashboard/src/main.js new file mode 100644 index 00000000..68e9a9bb --- /dev/null +++ b/Dashboard/src/main.js @@ -0,0 +1,49 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +const {app, Menu, BrowserWindow} = require('electron'); + +const path = require('path'); +const url = require('url'); + +app.setName('ExpoSE Dashboard'); + +let mainWindow; + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + minWidth: 800, + minHeight: 600 + }) + + // and load the index.html of the app. + mainWindow.loadURL(url.format({ + pathname: path.join(__dirname, '../content/index.html'), + protocol: 'file:', + slashes: true + })) + + mainWindow.on('closed', function () { + mainWindow = null; + }); +} + +app.on('ready', function() { + const menu = Menu.buildFromTemplate([]); + //Menu.setApplicationMenu(menu); + createWindow(); +}); + +app.on('window-all-closed', function () { + app.quit(); +}); + +app.on('activate', function () { + if (mainWindow === null) { + createWindow(); + } +}) diff --git a/Dashboard/src/output_parser.js b/Dashboard/src/output_parser.js new file mode 100644 index 00000000..422cbd0a --- /dev/null +++ b/Dashboard/src/output_parser.js @@ -0,0 +1,25 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +let expose = require('./expose'); + +module.exports = function(data) { + let match = expose.OUT_REGEX.exec(data); + if (match) { + let summary = JSON.parse(match[1]); + + summary.done.forEach(x => { + expose.coverage(x); + }); + + expose.sort(summary); + + return { + source: summary.source, + info: expose.summaryInfo(summary), + jobs: summary.done, + coverage: summary.done[summary.done.length - 1].coverage + }; + } +} diff --git a/Dashboard/src/replay.js b/Dashboard/src/replay.js new file mode 100644 index 00000000..bf23f1cc --- /dev/null +++ b/Dashboard/src/replay.js @@ -0,0 +1,42 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +const electron = require('electron'); +const app = electron.app; +const BrowserWindow = electron.BrowserWindow; +const path = require('path') +const url = require('url') + +let replays = []; + +function createReplay(file, input) { + let i = replays.length; + + let replay = new BrowserWindow({ + width: 400, + height: 800 + }); + + replay.replayFile = file; + replay.replayInput = input; + + // and load the index.html of the app. + replay.loadURL(url.format({ + pathname: path.join(__dirname, '../content/replay.html'), + protocol: 'file:', + slashes: true + })) + + replay.on('closed', function () { + replays[i] = null; + }); + + replays[i] = replay; + + replay.webContents.on('did-finish-load', function() { + replay.webContents.executeJavaScript(""); + }); +} + +module.exports = createReplay; diff --git a/Distributor/README.md b/Distributor/README.md new file mode 100644 index 00000000..d72ab48d --- /dev/null +++ b/Distributor/README.md @@ -0,0 +1,3 @@ +#Distributor + +Role is to schedule test process execution \ No newline at end of file diff --git a/Distributor/package.json b/Distributor/package.json new file mode 100644 index 00000000..a3237128 --- /dev/null +++ b/Distributor/package.json @@ -0,0 +1,20 @@ +{ + "name": "Distributor", + "version": "0.0.1", + "description": "", + "author": "Blake Loring ", + "readme": "README.md", + "dependencies": { + "walk": "*", + "tmp": "*", + "microtime": "*" + }, + "license": "Apache", + "main": "bin/Distributor.js", + "devDependencies": { + "babel-cli": "^6.18.0", + "babel-preset-babili": "0.0.9", + "babel-preset-es2015": "^6.3.13", + "babel-preset-stage-0": "^6.3.13" + } +} diff --git a/Distributor/src/Center.js b/Distributor/src/Center.js new file mode 100644 index 00000000..cfac7199 --- /dev/null +++ b/Distributor/src/Center.js @@ -0,0 +1,160 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Spawn from './Spawn'; +import Coverage from './CoverageAggregator'; + +class Center { + + constructor(options) { + this.cbs = []; + this.options = options; + } + + start(file) { + + this._lastid = 0; + this._done = []; + this._errors = 0; + this._running = 0; + this._coverage = new Coverage(); + + this._startTesting([{ + id: this._nextID(), + path: file, + input: { _bound: 0 } + }]); + + return this; + } + + done(cb) { + this.cbs.push(cb); + return this; + } + + _startTesting(files) { + this.files = files; + this._requeue(); + this._printStatus(); + } + + /** + * If there is a slot & under max paths start an additional test + */ + _startNext() { + if (this.files.length) { + //TODO: Have different strategies, or + //maybe different strategies running concurrently + this._testFile(this.files.shift()); + } + } + + /** + * True if another test can be queued + */ + _canQueue() { + return (this._done.length + this._running) < this.options.maxPaths && this._running < this.options.maxConcurrent; + } + + /** + * Queue as many tests as possible + */ + _requeue() { + while (this.files.length && this._canQueue()) { + this._startNext(); + } + } + + _postTest() { + this._running--; + + //Start any remaining queued + this._requeue(); + this._printStatus(); + + //If finished print output + if (this._running === 0) { + this._finishedTesting(); + } + } + + _printStatus() { + process.stdout.write('\r*** [' + this._done.length + ' done /' + this.files.length +' queued / ' + this._running + ' running / ' + this._errors + ' errors] ***'); + } + + _finishedTesting() { + this.cbs.forEach(cb => cb(this, this._done, this._errors, this._coverage)); + } + + _nextID() { + return this._lastid++; + } + + _expandAlternatives(file, alternatives) { + alternatives.forEach(alt => { + this.files.push({ + id: this._nextID(), + path: file.path, + input: alt.input, + pc: alt.pc + }); + }); + } + + _pushDone(test, input, pc, errors) { + + this._done.push({ + id: test.file.id, + input: input, + pc: pc, + errors: errors, + time: test.time(), + startTime: test.startTime(), + coverage: this._coverage.final(), + replay: test.makeReplayString() + }); + + if (errors.length) { + this._errors += 1; + } + } + + _testFileDone(code, test, finalOut, coverage, fsErrors) { + + let errors = fsErrors; + + if (code != 0) { + errors.push({error: 'Exit code non-zero'}); + } + + if (coverage) { + this._coverage.add(coverage); + } + + if (finalOut) { + this._pushDone(test, finalOut.input, finalOut.pc, errors.concat(finalOut.errors)); + this._expandAlternatives(test.file, finalOut.alternatives); + } else { + this._pushDone(test, test.file.input, test.file.pc, errors.concat([{ error: 'Error extracting final output - a fatal error must have occured' }])); + } + + this._postTest(); + } + + _testFile(file) { + this._running++; + + new Spawn(this.options.analyseScript, file, { + log: this.options.printPaths, + outFile: this.options.outFile, + timeout: this.options.testMaxTime, + coverageFile: this.options.coverageFile + }).start(this._testFileDone.bind(this)); + + this._printStatus(); + } +} + +export default Center; diff --git a/Distributor/src/CoverageAggregator.js b/Distributor/src/CoverageAggregator.js new file mode 100644 index 00000000..3089c954 --- /dev/null +++ b/Distributor/src/CoverageAggregator.js @@ -0,0 +1,73 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +class Coverage { + + constructor() { + this._current = {}; + } + + _getFile(file) { + if (!this._current[file]) { + this._current[file] = { + smap: [], + branches: [] + }; + } + return this._current[file]; + } + + _addSMap(f, smap) { + f.smap = smap; + } + + _mergeBranches(f, branches) { + for (let i in branches) { + f.branches[i] = 1; + } + } + + /** + * Merges new coverage data from a path with existing data + */ + add(coverage) { + for (let i in coverage) { + let file = this._getFile(i); + this._addSMap(file, coverage[i].smap); + this._mergeBranches(file, coverage[i].branches); + } + } + + _results(file) { + let found = 0; + let total = 0; + + for (let i in file.smap) { + total++; + found = file.branches[i] ? found + 1 : found; + } + + return { + found: found, + total: total, + coverage: found / total + } + } + + final() { + let results = []; + + for (let fidx in this._current) { + let file = this._getFile(fidx); + results.push({ + file: fidx, + data: this._results(file) + }); + } + + return results; + } +} + +export default Coverage; diff --git a/Distributor/src/Distributor.js b/Distributor/src/Distributor.js new file mode 100644 index 00000000..4195976e --- /dev/null +++ b/Distributor/src/Distributor.js @@ -0,0 +1,90 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Center from './Center'; +import microtime from 'microtime'; + +process.title = 'ExpoSE Distributor'; + +process.on('disconnect', function() { + Log.log('Premature termination - Parent exit') + process.exit(); +}); + +function getTarget() { + return process.argv[process.argv.length - 1]; +} + +function argToType(arg, type) { + if (type == 'number') { + return parseInt(arg); + } + return arg; +} + +function getArgument(name, type, dResult) { + return process.env[name] ? argToType(process.env[name], type) : dResult; +} + +if (process.argv.length >= 3) { + let target = getTarget(); + + let options = { + maxConcurrent: getArgument('EXPOSE_MAX_CONCURRENT', 'number', 10), //max number of tests to run concurrently + maxPaths: getArgument('EXPOSE_MAX_PATHS', 'number', 100), //Max paths spawned + jsonOut: getArgument('EXPOSE_JSON_OUT', 'number', false), //By default ExpoSE should not print JSON results into STDOUT + printPaths: getArgument('EXPOSE_PRINT_PATHS', 'number', false), //By default do not print paths to stdout + testMaxTime: getArgument('EXPOSE_TEST_TIMEOUT', 'number', 1000 * 60 * 15), //10 minutes default time + analyseScript: getArgument('EXPOSE_PLAY_SCRIPT', 'string', './scripts/play') + }; + + console.log('ExpoSE Master: ' + target + ' max concurrent: ' + options.concurrent + ' max paths: ' + options.maxPaths); + + let start = microtime.now(); + + new Center(options).done((center, done, errors, coverage) => { + + if (options.jsonOut) { + console.log('\nExpoSE JSON: ' + JSON.stringify({ + source: getTarget(), + start: start, + end: microtime.now(), + done: done + }) + '\nEND JSON'); + } else { + console.log(''); + } + + function round(num, precision) { + return Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision); + } + + function formatSeconds(v) { + return round((v / 1000 / 1000), 4) + } + + done.forEach(item => { + let testStartSeconds = item.startTime - start; + console.log('*-- Test Case ' + JSON.stringify(item.input) + ' start ' + formatSeconds(testStartSeconds) + ' took ' + formatSeconds(item.time) + 's'); + + if (item.errors.length != 0) { + console.log('*-- Errors occured in test ' + JSON.stringify(item.input)); + item.errors.forEach(error => console.log('* Error: ' + error.error)); + console.log('*-- Replay with ' + item.replay); + } + }); + + console.log('*-- Coverage Data'); + + coverage.final().forEach(d => { + console.log('*- File ' + d.file + '. Coverage: ' + Math.round(d.data.coverage * 100) + '%'); + }); + + console.log('** ExpoSE Finished. ' + done.length + ' paths with ' + errors + ' errors **'); + process.exitCode = errors; + }).start(target); +} else { + console.log('Wrong number of arguments'); + console.log('Usage Distributor --concurrent XX --max_paths 999 test_script'); +} diff --git a/Distributor/src/Spawn.js b/Distributor/src/Spawn.js new file mode 100644 index 00000000..a65b46a7 --- /dev/null +++ b/Distributor/src/Spawn.js @@ -0,0 +1,155 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import {spawn} from 'child_process'; +import microtime from 'microtime'; + +let StringDecoder = require('string_decoder').StringDecoder; +let tmp = require('tmp'); +let decoder = new StringDecoder('utf8'); +const fs = require('fs'); + +//Some number non zero to say process was killed +const PROCESS_KILLED = 999999; +const EXPOSE_REPLAY_PATH = 'NO_COMPILE=1 expoSE replay'; + +class Spawn { + + constructor(script, file, opts) { + this.script = script; + this.file = file; + this.options = opts; + this.args = [this.file.path, JSON.stringify(this.file.input)]; + this.tmpCoverageFile = tmp.fileSync(); + this.tmpOutFile = tmp.fileSync(); + + this.env = JSON.parse(JSON.stringify(process.env)); + this.env.EXPOSE_OUT_PATH = this.tmpOutFile.name; + this.env.EXPOSE_COVERAGE_PATH = this.tmpCoverageFile.name; + } + + _tryParse(data, type, errors) { + try { + return JSON.parse(data); + } catch (e) { + errors.push({error: 'Exception E: ' + e + ' of ' + type + ' on ' + data}); + return null; + } + } + + startTime() { + return this._startTime; + } + + endTime() { + return this._endTime; + } + + time() { + return this._endTime - this._startTime; + } + + _recordEndTime() { + this._endTime = microtime.now(); + } + + _processKilled(done) { + this._recordEndTime(); + done(PROCESS_KILLED, this, null, null, ['Process terminated due to duration hitting timeout']); + } + + _processEnded(code, done) { + + this._recordEndTime(); + + let errors = []; + let coverage = null; + let finalOut = null; + let count = 0; + let test = this; + + function cb(err) { + + count++; + + if (err) { + errors.push({error: err}); + } + + if (count == 2) { + test.tmpOutFile.removeCallback(); + test.tmpCoverageFile.removeCallback(); + done(code, test, finalOut, coverage, errors); + } + } + + fs.readFile(this.tmpOutFile.name, {encoding: 'utf8'}, function(err, data) { + if (!err) { + finalOut = test._tryParse(data, 'test data', errors); + } + cb(err); + }); + + fs.readFile(this.tmpCoverageFile.name, {encoding: 'utf8'}, function(err, data) { + if (!err) { + coverage = test._tryParse(data, 'coverage data', errors); + } + cb(err); + }); + } + + shellescape(a) { + let ret = []; + + a.forEach(function(s) { + if (/[^A-Za-z0-9_\/:=-]/.test(s)) { + s = "'" + s.replace(/'/g,"'\\''") + "'"; + } + ret.push(s); + }); + + return ret.join(' '); + } + + makeReplayString() { + return EXPOSE_REPLAY_PATH + ' ' + this.shellescape(this.args); + } + + _buildTimeout(prc, done) { + return setTimeout(() => { + prc.stdin.pause(); + prc.kill(); + this._processKilled(done); + }, this.options.timeout); + } + + start(done) { + + function insertData(data) { + data = decoder.write(data); + if (this.options.log) { + process.stdout.write(data); + } + } + + let prc = spawn(this.script, this.args, { + env: this.env, + disconnected: false + }); + + this._startTime = microtime.now(); + + this._killTimeout = this._buildTimeout(prc, done); + + prc.stdout.on('data', insertData.bind(this)); + prc.stderr.on('data', insertData.bind(this)); + + prc.stdout.on('close', code => { + clearTimeout(this._killTimeout); + this._processEnded(code, done); + }); + } +} + +export default Spawn; diff --git a/ElectronDriver/package.json b/ElectronDriver/package.json new file mode 100644 index 00000000..3cdc0cd5 --- /dev/null +++ b/ElectronDriver/package.json @@ -0,0 +1,21 @@ +{ + "name": "electron-driver", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "scripts": { + "start": "electron ." + }, + "author": "", + "license": "ISC", + "dependencies": { + "async": "^2.1.5", + "browserify": "^14.1.0", + "electron": "1.3.4", + "jalangi2": "git+https://github.com/Samsung/jalangi2.git", + "z3javascript": "git+https://github.com/jawline/z3javascript.git" + }, + "devDependencies": { + "electron-rebuild": "^1.5.7" + } +} diff --git a/ElectronDriver/src/main.js b/ElectronDriver/src/main.js new file mode 100644 index 00000000..3f29b579 --- /dev/null +++ b/ElectronDriver/src/main.js @@ -0,0 +1,45 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +const {app, Menu, BrowserWindow} = require('electron'); + +const path = require('path'); +const url = require('url'); + +let mainWindow; + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + minWidth: 800, + minHeight: 600 + }) + + // and load the index.html of the app. + mainWindow.loadURL(url.format({ + pathname: "example.com", + protocol: 'http:', + slashes: true + })) + + mainWindow.on('closed', function () { + mainWindow = null; + }); +} + +app.on('ready', function() { + const menu = Menu.buildFromTemplate([]); + //Menu.setApplicationMenu(menu); + createWindow(); +}); + +app.on('window-all-closed', function () { + app.quit(); +}); + +app.on('activate', function () { + if (mainWindow === null) { + createWindow(); + } +}) diff --git a/ElectronDriver/test/index.html b/ElectronDriver/test/index.html new file mode 100644 index 00000000..6f520aec --- /dev/null +++ b/ElectronDriver/test/index.html @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..8c23b117 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2017 Royal Holloway, University of London + +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..6ad34a04 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +## ExpoSE.js + +A simple symbolic execution engine for JavaScript, built using _Jalangi 2_. + +### Installation + +Requires `node` version v7.5.0 (Others should work but no support is guaranteed), `npm`, `clang`, `clang++`, `gnuplot`, `make`, `mitmproxy` (Depends libxml2-dev, libxslt-dev, libssl-dev), `python2` (as python in path). + +To build all other dependencies and install, execute + +```sh +$ npm install +``` + +from the command line. + +Note: no administrator privileges are required, but the environment assumes either Mac OS X or a standard(ish) Linux distribution. + +This will also add ExpoSE to your path, restarting your terminal should allow expoSE to be executed from anywhere. + +### ExpoSE GUI + +Start the ExpoSE dashboard with + +```sh +$ npm start +``` + +### ExpoSE CLI + +All basic ExpoSE functionality is exposed through the expoSE CLI script. Valid options are `setup`, `test`, `replay` and `test_suite`. + +`setup` sets up the environment and pulls most dependencies. +`test` symbolically executes a given test. +`replay` replays a test case with a specific input. +Finally `test_suite` is used to test whether any changes break the interpreter. + +### Run without rebuilding + +The environment flag NO_COMPILE=1 will make the expoSE script stop recompiling the entire framework between runs (Useful if no source changes are being made) + +Example: + +```sh +$ NO_COMPILE=1 expoSE target/hello.js +``` + +NOTE: Logging instructions are removed from the output at compile time so this command conflicts with EXPOSE_LOG_LEVEL being changed + +### Configuration + +All environment flags work both with the ExpoSE Dashboard and ExpoSE CLI. Typically these can be set from a terminal by writing a command such as + +```sh +$ EXPOSE_LOG_LEVEL=1 expoSE test target/hello.js +``` + +`EXPOSE_PRINT_PATHS` - Print the output of each test case to stdout + +`EXPOSE_LOG_LEVEL` - Level from 0 (None) to 3 (HIGH) + +`EXPOSE_MAX_CONCURRENT` - The maximum number of test cases that can run concurrently + +`EXPOSE_TEST_TIMEOUT` - The time (in milliseconds) a test case can run for before being timed out + +`EXPOSE_MAX_PATHS` - The maximum number of test cases to execute + +### Setup without Cleanup + +In some rare cases you may want to rerun setup without the cleanup script executing. The environment flag NO_CLEANUP=1 can be used to force this. + +```sh +$ NO_CLEANUP=1 ./expoSE setup +``` diff --git a/Tester/README.md b/Tester/README.md new file mode 100644 index 00000000..0d404ab3 --- /dev/null +++ b/Tester/README.md @@ -0,0 +1,11 @@ +#Tester + +Test runner for the analyser, runs all the files in a folder and reports errors + +#Running +Use the test_runner script in the top level directory to run the default test suite + +#Custom Directory +To run a custom test suite simply invoke bin/TestRunner.js DIRNAME, where DIRNAME is replace with the directory of your test suite. + +A test suite requires a testlist JS file, an example can be found in tests/ diff --git a/Tester/package.json b/Tester/package.json new file mode 100644 index 00000000..ede0f7db --- /dev/null +++ b/Tester/package.json @@ -0,0 +1,18 @@ +{ + "name": "Tester", + "version": "0.0.1", + "description": "Test runner for the symbolic execution machine", + "author": "Blake Loring ", + "readme": "README.md", + "dependencies": { + "walk": "*" + }, + "license": "Apache", + "main": "src/TestRunner.js", + "devDependencies": { + "babel-cli": "^6.18.0", + "babel-preset-babili": "0.0.9", + "babel-preset-es2015": "^6.3.13", + "babel-preset-stage-0": "^6.3.13" + } +} \ No newline at end of file diff --git a/Tester/src/Runner.js b/Tester/src/Runner.js new file mode 100644 index 00000000..7aec2dec --- /dev/null +++ b/Tester/src/Runner.js @@ -0,0 +1,97 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Tester from './Tester'; +import Walker from './Walker'; + +class Runner { + + constructor(maxConcurrent) { + this.cbs = []; + this._maxConcurrent = maxConcurrent; + } + + start(dir) { + this._errors = 0; + this._running = 0; + this.walker = new Walker(dir).done(files => this.startTesting(files)).start(); + return this; + } + + done(cb) { + this.cbs.push(cb); + return this; + } + + startTesting(files) { + this.files = files; + this.filesTotal = files.length; + this.done = 0; + + this._printStatus(); + + //Start the tests + for (let i = 0; i < this._maxConcurrent; i++) { + this.startNext(); + } + } + + /** + * Start any remaining tests + */ + startNext() { + if (this.files.length) { + this.testFile(this.files.pop()); + } + } + + postTest() { + + this._running--; + + //Start any remaining queued + this.startNext(); + this._printStatus(); + + //If finished print output + if (this._running === 0) { + this.finishedTesting(); + } + } + + _printStatus() { + process.stdout.write('\r*** [' + this.done + '/' + this.filesTotal +'] [' + this._running + ' running] [' + this._errors + ' errors] ***'); + } + + finishedTesting() { + console.log('\n**************************'); + console.log('* Summary *'); + console.log('**************************'); + console.log('* ' + this.done + ' complete *'); + console.log('* ' + this._errors + ' errors *'); + console.log('**************************'); + this.cbs.forEach(cb => cb(this._errors)); + } + + _testFileDone(test, code, file) { + this.done++; + + if (code !== file.expectErrors) { + process.stderr.write('\n' + file.path + ' failed with errors (' + code + '). Printing output\n'); + process.stderr.write(test.out + '\n'); + this._errors++; + } + + this.postTest(); + } + + testFile(file) { + this._running++; + let test = new Tester(file); + this._printStatus(); + test.build(code => this._testFileDone(test, code, file)); + } +} + +export default Runner; diff --git a/Tester/src/TestRunner.js b/Tester/src/TestRunner.js new file mode 100644 index 00000000..7e142584 --- /dev/null +++ b/Tester/src/TestRunner.js @@ -0,0 +1,36 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Runner from './Runner'; + +process.title = 'ExpoSE Test Runner'; + +function getTarget() { + return process.argv[process.argv.length - 1]; +} + +function getArgument(name, dResult) { + for (let i = 0; i < process.argv.length - 1; i++) { + if (process.argv[i] === name) { + return process.argv[i+1]; + } + } + + return dResult; +} + +if (process.argv.length >= 3) { + let target = getTarget(); + + console.log('Test runner searching ' + target); + + let concurrent = getArgument('--concurrent', 10); + + console.log('Launching with max concurrent of ' + concurrent); + + new Runner(concurrent).done(errors => process.exit(errors)).start(target); +} else { + console.log('Wrong number of arguments'); + console.log('Usage ./TestRunner --concurrent XX Directory'); +} diff --git a/Tester/src/Tester.js b/Tester/src/Tester.js new file mode 100644 index 00000000..a49b7305 --- /dev/null +++ b/Tester/src/Tester.js @@ -0,0 +1,34 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import {spawn} from 'child_process'; + +const EXPOSE_TEST_SCRIPT = './scripts/analyse'; + +class Tester { + + constructor(file) { + this.file = file; + this.out = ""; + } + + build(done) { + + let env = process.env; + env.EXPOSE_EXPECTED_PC = this.file.expectPaths; + + let prc = spawn(EXPOSE_TEST_SCRIPT, [this.file.path], { + env: env + }); + + prc.stdout.setEncoding('utf8'); + prc.stderr.setEncoding('utf8'); + prc.stdout.on('data', data => this.out += data.toString()); + prc.stderr.on('data', data => this.out += data.toString()); + + prc.on('close', code => done(code)); + } +} + +export default Tester; diff --git a/Tester/src/Walker.js b/Tester/src/Walker.js new file mode 100644 index 00000000..353097f9 --- /dev/null +++ b/Tester/src/Walker.js @@ -0,0 +1,38 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import walk from 'walk'; +import path from 'path'; + +class Walker { + constructor(dir) { + this.cbs = []; + this.dir = dir; + } + + start() { + let absTestFilePath = path.resolve(this.dir + 'test_list'); + let fileList = require(absTestFilePath); + + this.files = fileList.map(f => ({ + path: this.dir + f.path, + expectErrors: f.expectErrors, + expectPaths: f.expectPaths + })); + + this.doneSearching(); + return this; + } + + doneSearching() { + this.cbs.forEach(cb => cb(this.files)); + } + + done(cb) { + this.cbs.push(cb); + return this; + } +} + +export default Walker; diff --git a/expoSE b/expoSE new file mode 100755 index 00000000..1c382305 --- /dev/null +++ b/expoSE @@ -0,0 +1,41 @@ +#!/bin/bash + +#If command is setup run setup script and exit +if [ "$1" == "setup" ]; then + ./scripts/setup/setup + exit $? +fi + +#Store the current working directory to return after calculating abspath +ORIGIN_PATH="$(pwd)" + +#Generate the absolute path of test script as it will be lost on cd into ExpoSE +cd "$(dirname "$2")" +TARGET_REAL_PATH="$(printf "%s/%s\n" "$(pwd)" "$(basename "$2")")" + +cd $ORIGIN_PATH + +#cd to ExpoSE directory +cd "$(dirname "${BASH_SOURCE[0]}")" + +if [ -z ${NO_COMPILE} ]; then + ./scripts/build/build_analyser + if [ $? -ne 0 ]; then + exit 1 + fi +fi + +if [ "$1" == "test" ]; then + ./scripts/analyse $TARGET_REAL_PATH "${@:3}" + exit $? +elif [ "$1" == "replay" ]; then + source ./scripts/expose_env + ./scripts/play $TARGET_REAL_PATH "${@:3}" + exit $? +elif [ "$1" == "test_suite" ]; then + ./scripts/run_tests + exit $? +else + echo "Error invalid command, options are setup, test, replay and test_suite" + exit 1 +fi \ No newline at end of file diff --git a/lib/Mocker/README.md b/lib/Mocker/README.md new file mode 100644 index 00000000..3c67a35c --- /dev/null +++ b/lib/Mocker/README.md @@ -0,0 +1,3 @@ +#Mocker + +ExpoSE environment mocking framework \ No newline at end of file diff --git a/lib/Mocker/package.json b/lib/Mocker/package.json new file mode 100644 index 00000000..634fd62c --- /dev/null +++ b/lib/Mocker/package.json @@ -0,0 +1,16 @@ +{ + "name": "Mocker", + "version": "0.0.1", + "description": "Javascript value annotations", + "author": "Blake Loring ", + "readme": "README.md", + "dependencies": {}, + "main": "bin/Mocker.js", + "license": "Apache", + "devDependencies": { + "babel-cli": "^6.18.0", + "babel-preset-babili": "0.0.9", + "babel-preset-es2015": "^6.3.13", + "babel-preset-stage-0": "^6.3.13" + } +} diff --git a/lib/Mocker/src/Handler.js b/lib/Mocker/src/Handler.js new file mode 100644 index 00000000..d72261dd --- /dev/null +++ b/lib/Mocker/src/Handler.js @@ -0,0 +1,13 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +class Handler { + shouldHandle(state, request) { + return false; + } + + handle(state, request, callback) {} +} + +export default Handler; diff --git a/lib/Mocker/src/Mocker.js b/lib/Mocker/src/Mocker.js new file mode 100644 index 00000000..ac73b6a1 --- /dev/null +++ b/lib/Mocker/src/Mocker.js @@ -0,0 +1,22 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Network from './Network/Network'; +import Handler from './Handler'; +import MockerUtilities from './MockerUtilities'; +import State from './State'; +import Polyfills from './Polyfills/Polyfills'; + +function Mocker(object, index, val) { + return MockerUtilities.replace(object, index, val); +} + +Mocker.Utilities = MockerUtilities; +Mocker.Network = Network; +Mocker.Handler = Handler; +Mocker.State = State; +Mocker.Polyfills = Polyfills; + +export default Mocker; +module.exports = Mocker; diff --git a/lib/Mocker/src/MockerUtilities.js b/lib/Mocker/src/MockerUtilities.js new file mode 100644 index 00000000..ba2f7a57 --- /dev/null +++ b/lib/Mocker/src/MockerUtilities.js @@ -0,0 +1,12 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Request from './Network/NetworkRequest'; + +export default { + replace: function(object, index, value) { + object[index] = value; + }, + nop: function() {} +} diff --git a/lib/Mocker/src/Network/Examples/LightApi.js b/lib/Mocker/src/Network/Examples/LightApi.js new file mode 100644 index 00000000..30987680 --- /dev/null +++ b/lib/Mocker/src/Network/Examples/LightApi.js @@ -0,0 +1,48 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import NetworkState from '../NetworkState'; +import Handler from '../../Handler'; + +const States = { + Off: 0, + On: 1 +}; + +class LoginHandler extends Handler { + shouldHandle(state, request) { + return request.getUrl() === '/login'; + } + + handle(state, request, callback) { + let key = '' + Math.random(); + state._keys.push(key) + callback('' + key, undefined); + } +} + +class SetStateHandler extends Handler { + shouldHandle(state, request) { + return request.getUrl() === '/setstate'; + } + + handle(state, request, callback) { + if (state._keys.find(x => x === request.data.ukey)) { + if (request.data.dstate === 'ON') { + state.setState(States.On); + } else if (request.data.dstate === 'OFF') { + state.setState(States.Off); + } + } + } +} + +class LoginRest extends NetworkState { + constructor() { + super(States.Off, [new LoginHandler(), new SetStateHandler()]); + this._keys = []; + } +} + +export default LoginRest; diff --git a/lib/Mocker/src/Network/Examples/LoginRest.js b/lib/Mocker/src/Network/Examples/LoginRest.js new file mode 100644 index 00000000..f5f10997 --- /dev/null +++ b/lib/Mocker/src/Network/Examples/LoginRest.js @@ -0,0 +1,44 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import NetworkState from '../NetworkState'; +import Handler from '../../Handler'; + +const States = { + NotLoggedIn: 0, + LoggedIn: 1 +}; + +class LoginHandler extends Handler { + shouldHandle(state, request) { + return request.getUrl() === '/login'; + } + + handle(state, request, callback) { + state.setState(States.LoggedIn); + callback('OK', undefined); + } +} + +class GetDataHandler extends Handler { + shouldHandle(state, request) { + return request.getUrl() === '/data'; + } + + handle(state, request, callback) { + if (state.getState() === States.LoggedIn) { + callback({some: 'Data'}); + } else { + callback(undefined, 'NotLoggedIn'); + } + } +} + +class LoginRest extends NetworkState { + constructor() { + super(States.NotLoggedIn, [new LoginHandler(), new GetDataHandler()]); + } +} + +export default LoginRest; diff --git a/lib/Mocker/src/Network/Examples/LoginRestHarnass.js b/lib/Mocker/src/Network/Examples/LoginRestHarnass.js new file mode 100644 index 00000000..0312dd13 --- /dev/null +++ b/lib/Mocker/src/Network/Examples/LoginRestHarnass.js @@ -0,0 +1,26 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import LoginRest from './LoginRest'; +import NetworkRequest from '../NetworkRequest'; + +let RestServer = new LoginRest(); + +function sendMessage(url, data, doneCallback, errorCallback) { + let request = new NetworkRequest(url, data); + + function hRes(data, err) { + if (err) { + errorCallback(err); + } else { + doneCallback(data); + } + } + + RestServer.handleRequest(request, hRes); +} + +sendMessage('/data', null, function(data) { throw 'Should not ever be executed'; }, function(err) { console.log('First data failed'); }); +sendMessage('/login', null, function(data) { console.log('Logged In'); }, function(err) { throw 'Should not be hit'; }) +sendMessage('/data', null, function(data) { console.log('Got the data ' + data); }, function(err) { throw 'Should not be hit'; }); diff --git a/lib/Mocker/src/Network/Network.js b/lib/Mocker/src/Network/Network.js new file mode 100644 index 00000000..478e91e8 --- /dev/null +++ b/lib/Mocker/src/Network/Network.js @@ -0,0 +1,11 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import NetworkState from './NetworkState'; +import NetworkRequest from './NetworkRequest'; + +export default { + State: NetworkState, + Request: NetworkRequest +} diff --git a/lib/Mocker/src/Network/NetworkRequest.js b/lib/Mocker/src/Network/NetworkRequest.js new file mode 100644 index 00000000..10efe773 --- /dev/null +++ b/lib/Mocker/src/Network/NetworkRequest.js @@ -0,0 +1,20 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +class NetworkRequest { + constructor(url, data) { + this._url = url; + this._data = data; + } + + getUrl() { + return this._url; + } + + getData() { + return this._data; + } +} + +export default NetworkRequest; diff --git a/lib/Mocker/src/Network/NetworkState.js b/lib/Mocker/src/Network/NetworkState.js new file mode 100644 index 00000000..78709e5c --- /dev/null +++ b/lib/Mocker/src/Network/NetworkState.js @@ -0,0 +1,23 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import State from '../State.js'; + +class NetworkState extends State { + constructor(initial, handlers) { + super(initial); + this._handlers = handlers; + } + + _eachWhere(cpredicate, dpredicate) { + this._handlers.forEach(handler => { if (cpredicate(handler)) { dpredicate(handler); }}); + } + + handleRequest(request, cb) { + this._eachWhere(handler => handler.shouldHandle(this, request), + handler => handler.handle(this, request, cb)); + } +} + +export default NetworkState; diff --git a/lib/Mocker/src/Polyfills/ArrayFind.js b/lib/Mocker/src/Polyfills/ArrayFind.js new file mode 100644 index 00000000..734d06f6 --- /dev/null +++ b/lib/Mocker/src/Polyfills/ArrayFind.js @@ -0,0 +1,35 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +//From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find +export default { + setup: function() { + if (!Array.prototype.find) { + Array.prototype.find = function(predicate) { + + if (this === null) { + throw new TypeError('Array.prototype.find called on null or undefined'); + } + + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + + var list = Object(this); + var length = list.length >>> 0; + var thisArg = arguments[1]; + var value; + + for (var i = 0; i < length; i++) { + value = list[i]; + if (predicate.call(thisArg, value, i, list)) { + return value; + } + } + + return undefined; + }; + } + } +}; diff --git a/lib/Mocker/src/Polyfills/NoQuery.js b/lib/Mocker/src/Polyfills/NoQuery.js new file mode 100644 index 00000000..c4094127 --- /dev/null +++ b/lib/Mocker/src/Polyfills/NoQuery.js @@ -0,0 +1,78 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Request from '../Network/NetworkRequest'; + +export default { + + connectAjax: function(NoQuery, networkState) { + NoQuery.ajax = function(info) { + + let localRequest; + + if (typeof info === 'string') { + localRequest = new Request(info, argument[1].data); + } else { + localRequest = new Request(info.url, info.data); + } + + networkState.handleRequest(localRequest, info.success); + }; + }, + + setup: function() { + let NoQuery = function() { + return { + html: function() { + return 'html'; + }, + submit: function(fn) { + return NoQuery; + }, + val: function() { + return 'val'; + }, + click: NoQuery, + keyup: NoQuery, + css: NoQuery, + attr: NoQuery, + animate: function(detail, r, cb) { + if (cb) { + cb(); + } + return NoQuery; + }, + keydown: NoQuery, + ajaxError: NoQuery, + mouseout: NoQuery, + mouseover: NoQuery, + focus: NoQuery, + select: NoQuery, + fadeIn: function(speed, cb) { + if (cb) { + cb(); + } + return NoQuery; + }, + fadeOut: function(speed, cb) { + if (cb) { + cb(); + } + return NoQuery; + } + }; + } + + NoQuery.trim = function(v) { + return v.trim(); + }; + + NoQuery.inArray = function(v, a) { + return a.indexOf(v); + }; + + global.$ = NoQuery; + global.jQuery = NoQuery; + } +}; diff --git a/lib/Mocker/src/Polyfills/NodeDocument.js b/lib/Mocker/src/Polyfills/NodeDocument.js new file mode 100644 index 00000000..4bb97aac --- /dev/null +++ b/lib/Mocker/src/Polyfills/NodeDocument.js @@ -0,0 +1,32 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +class Element { + Element(type, id) { + this._type = type; + this._id = id; + } + + compareDocumentPosition() { + return 1; + } +} + +class Document { + createElement(type) { + return new Element(); + } + + getElementById(id) { + return new Element('div', id); + } +} + +Document.prototype.documentElement = new Element('html'); + +export default { + setup: function() { + global.document = new Document(); + } +} diff --git a/lib/Mocker/src/Polyfills/NodeHttpRequest.js b/lib/Mocker/src/Polyfills/NodeHttpRequest.js new file mode 100644 index 00000000..13e431c1 --- /dev/null +++ b/lib/Mocker/src/Polyfills/NodeHttpRequest.js @@ -0,0 +1,36 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +class XMLHttpRequest { + getResponseHeader() { + return 'SOMESTRINGOFSTUFF'; + } + + getAllResponseHeaders() { + return 'SOMESTRINGOFSTUFF'; + } + + open() {} + + send() { + if (this.onreadystatechange) { + this.onreadystatechange(); + } + } + + abort() {} +} + +XMLHttpRequest.prototype.onreadystatechange = null; +XMLHttpRequest.prototype.readyState = 4; + +XMLHttpRequest.prototype.response = "DUMMYRESPONSE"; +XMLHttpRequest.prototype.responseText = "DUMMYRESPONSE"; +XMLHttpRequest.prototype.responseType = "text"; + +export default { + setup: function() { + global.XMLHttpRequest = XMLHttpRequest; + } +}; diff --git a/lib/Mocker/src/Polyfills/NodeNavigator.js b/lib/Mocker/src/Polyfills/NodeNavigator.js new file mode 100644 index 00000000..e6bc21c5 --- /dev/null +++ b/lib/Mocker/src/Polyfills/NodeNavigator.js @@ -0,0 +1,13 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +class Navigator {} + +Navigator.prototype.userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36"; + +export default { + setup: function() { + global.navigator = new Navigator(); + } +} diff --git a/lib/Mocker/src/Polyfills/NodePolyfills.js b/lib/Mocker/src/Polyfills/NodePolyfills.js new file mode 100644 index 00000000..955d5bd9 --- /dev/null +++ b/lib/Mocker/src/Polyfills/NodePolyfills.js @@ -0,0 +1,15 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import NodeHTTPRequest from './NodeHttpRequest'; +import NodeWindow from './NodeWindow'; +import NodeNavigator from './NodeNavigator'; + +export default { + setup: function() { + NodeWindow.setup(); + NodeHTTPRequest.setup(); + NodeNavigator.setup(); + } +}; diff --git a/lib/Mocker/src/Polyfills/NodeWindow.js b/lib/Mocker/src/Polyfills/NodeWindow.js new file mode 100644 index 00000000..a595a1ff --- /dev/null +++ b/lib/Mocker/src/Polyfills/NodeWindow.js @@ -0,0 +1,20 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import NodeDocument from './NodeDocument'; +import Utils from '../MockerUtilities'; + +export default { + setup: function() { + global.window = GLOBAL; + + global.addEventListener = Utils.nop; + global.attachEvent = Utils.nop; + + global.removeEventListener = Utils.nop; + global.detachEvent = Utils.nop; + + NodeDocument.setup(); + } +}; diff --git a/lib/Mocker/src/Polyfills/Polyfills.js b/lib/Mocker/src/Polyfills/Polyfills.js new file mode 100644 index 00000000..c20beb37 --- /dev/null +++ b/lib/Mocker/src/Polyfills/Polyfills.js @@ -0,0 +1,15 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import NodePolyfills from './NodePolyfills'; +import NoQuery from './NoQuery'; +import StartsWith from './StartsWith'; +import ArrayFind from './ArrayFind'; + +export default { + Node: NodePolyfills, + NoQuery: NoQuery, + StartsWith: StartsWith, + ArrayFind: ArrayFind +} diff --git a/lib/Mocker/src/Polyfills/StartsWith.js b/lib/Mocker/src/Polyfills/StartsWith.js new file mode 100644 index 00000000..86aa862c --- /dev/null +++ b/lib/Mocker/src/Polyfills/StartsWith.js @@ -0,0 +1,14 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +export default { + setup: function() { + if (!String.prototype.startsWith) { + String.prototype.startsWith = function(searchString, position){ + position = position || 0; + return this.substr(position, searchString.length) === searchString; + }; + } + } +}; diff --git a/lib/Mocker/src/State.js b/lib/Mocker/src/State.js new file mode 100644 index 00000000..012fd746 --- /dev/null +++ b/lib/Mocker/src/State.js @@ -0,0 +1,19 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +class State { + constructor(initial) { + this._current = initial; + } + + setState(newState) { + this._current = newState; + } + + getState() { + return this._current; + } +} + +export default State; diff --git a/lib/S$/README.md b/lib/S$/README.md new file mode 100644 index 00000000..e69de29b diff --git a/lib/S$/package.json b/lib/S$/package.json new file mode 100644 index 00000000..e1a5178b --- /dev/null +++ b/lib/S$/package.json @@ -0,0 +1,16 @@ +{ + "name": "SDollar", + "version": "0.0.1", + "description": "Javascript Assertion Library", + "author": "Blake Loring ", + "readme": "README.md", + "dependencies": {}, + "main": "bin/symbols.js", + "license": "Apache", + "devDependencies": { + "babel-cli": "^6.18.0", + "babel-preset-babili": "0.0.9", + "babel-preset-es2015": "^6.3.13", + "babel-preset-stage-0": "^6.3.13" + } +} \ No newline at end of file diff --git a/lib/S$/src/symbols.js b/lib/S$/src/symbols.js new file mode 100644 index 00000000..01c36582 --- /dev/null +++ b/lib/S$/src/symbols.js @@ -0,0 +1,249 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"no_prelude"; +"use strict"; + +let Traits; + +try { + Traits = require('Traits'); +} catch (e) { + console.log('Traits not present'); +} + + /** + * Interpretter hooks + */ +function __not__error_exp__() {} +function __clone__(v) {} +function __make__symbolic__(n, c) {} +function __wrap__(v) {} +function __get__rider__(v) {} +function __set__rider__(v, t) {} +function __mark_safe_native__(f) {} + +var HIDDEN_WRAP_MARKER = '__hidden_wrapped__'; + +/** + * If this method is passed a single argument it wraps in a WrappedValue, + * If this message is passed two arguments (name, concrete) it creates a new symbol + */ +function AssertToolkit() { + AssertToolkit.fail('Should not be invoked directly'); +} + +if (Traits) { + AssertToolkit.Traits = Traits; + AssertToolkit.Top = Traits.Trait.create('', []); +} + +AssertToolkit.isPrimitive = function(val) { + + switch (typeof val) { + case "undefined": + case "number": + case "boolean": + case "string": + return true; + } + + if (val === null) { + return true; + } + + return false; +} + +AssertToolkit.symbolsNameBin = {}; + +/** + * Resolves a symbol name by the number of times in this execution that symbol has already existed + */ +AssertToolkit.resolveSymbolName = function(name) { + var nextId = (AssertToolkit.symbolsNameBin[name] = AssertToolkit.symbolsNameBin[name] ? AssertToolkit.symbolsNameBin[name] + 1 : 1); + if (nextId != 1) { + return (name + nextId); + } else { + return name; + } +} + +AssertToolkit.markSafe = __mark_safe_native__; + +/** + * Create a new symbol with a given name and initial concrete value + * TODO: Note: _ in name are disallowed due to name resolution and should be validated (In tropigate possibly rather than here) + */ +AssertToolkit.symbol = function(name, c) { + return __make__symbolic__(AssertToolkit.resolveSymbolName(name), c); +} + +/** + * Creates a new 'pure' symbol with an unknown type (Enumerates all current theories to create sample values) + */ +AssertToolkit.pureSymbol = function(name) { + var name_type = AssertToolkit.symbol(name + "_type", 0); + switch (name_type) { + case 0: + //TODO: Symbolic Undef? + return undefined; + case 1: + //TODO: Symbolic Null? + return null; + case 2: + return AssertToolkit.symbol(name + '_int', 0); + case 3: + return AssertToolkit.symbol(name + '_string', 'PureString'); + default: + return AssertToolkit.symbol(name + '_bool', false); + } +} + +/** + * Wrap a value in a WrappedValue class + */ +AssertToolkit.wrap = function(value) { + if (AssertToolkit.isPrimitive(value)) { + return __wrap__(value); + } else { + value[HIDDEN_WRAP_MARKER] = __wrap__(true); + return value; + } +} + +AssertToolkit.fresh_clone = function(value) { + return __clone__(value); +} + +AssertToolkit.clone = function(value) { + var rider = __get__rider__(value); + var clone = AssertToolkit.fresh_clone(value); + __set__rider__(clone, rider); + return clone; +} + +/** + * Expose the annotations to the tests + */ +AssertToolkit.NotAnErrorException = __not__error_exp__(); +AssertToolkit.GeneralError = function(x) { return x; } + +AssertToolkit.mk = function(c, args) { + + function F(args) { + return c.apply(this, args); + } + + F.prototype = c.prototype; + + return new F(args); +} + +AssertToolkit.t = function(c, ...args) { + return AssertToolkit.mk(c, args); +} + +AssertToolkit.asRider = function(val, t) { + var rider = __get__rider__(val); + rider = rider ? rider.generateAs(t) : t; + __set__rider__(val, rider); +} + +AssertToolkit.dropRider = function(val, t) { + var rider = __get__rider__(val); + rider = rider ? rider.generateDrop(t) : t; + __set__rider__(val, rider); +} + +AssertToolkit.isRider = function(val, t) { + var rider = __get__rider__(val); + return rider ? rider.isSubtypeOf(t) : false; +} + +AssertToolkit.getRider = function(val) { + return __get__rider__(val); +} + +/** + * The program will only continue past this point if the boolean condition is true + * Returns an object with is on it which can be used to annotate wrapped values + */ +AssertToolkit.assume = function(condition) { + + condition = AssertToolkit.wrap(condition); + + function ist(type) { + AssertToolkit.asRider(AssertToolkit.getWrappedPortion(condition), type); + return condition; + } + + function dropt(type) { + AssertToolkit.dropRider(AssertToolkit.getWrappedPortion(condition), type); + return condition; + } + + return { + true: function() { + if (!condition) { + throw new AssertToolkit.NotAnErrorException(); + } + }, + false: function() { + if (condition) { + throw new AssertToolkit.NotAnErrorException(); + } + }, + is: ist, + drop: dropt + } +} + +AssertToolkit.getWrappedPortion = function(val) { + + if (!AssertToolkit.isPrimitive(val) && val[HIDDEN_WRAP_MARKER]) { + return val[HIDDEN_WRAP_MARKER]; + } + + return val; +} + +/** + * Immediately fail the running script for a given reason + */ +AssertToolkit.fail = function(reason) { + throw AssertToolkit.GeneralError(reason); +} + +AssertToolkit.test = function(val) { + return { + is: function(type) { + return AssertToolkit.isRider(AssertToolkit.getWrappedPortion(val), type); + } + }; +} + +/** + * If supplied a single argument the method runs _constructAssertion to generate an object with is, equals and doesntEqual methods + * If supplied two arguments the method asserts that the first is truthy and if it isn't fails with reason desc + */ +AssertToolkit.assert = function(value, desc) { + if (!value) { + if (desc instanceof Function) { + desc = desc(); + } + AssertToolkit.fail(desc); + } +} + +/* + * Models for safe to native functions need their own little place + */ + + AssertToolkit.markSafe(Array.prototype.push); + AssertToolkit.markSafe(Array.prototype.keys); + AssertToolkit.markSafe(Array.prototype.forEach); + AssertToolkit.markSafe(Array.prototype.filter); + AssertToolkit.markSafe(Array.prototype.map); + +export default AssertToolkit; +module.exports = exports["default"]; diff --git a/lib/Tropigate/README.md b/lib/Tropigate/README.md new file mode 100644 index 00000000..33a6749d --- /dev/null +++ b/lib/Tropigate/README.md @@ -0,0 +1,8 @@ +#Setup +`./install` + +#Running +`./tropigate FILENAME [OUTFILENAME]` + +#To Stop Injection +`TROP_DO_INJECT=NO ./tropigate FILENAME` \ No newline at end of file diff --git a/lib/Tropigate/build_tropigate b/lib/Tropigate/build_tropigate new file mode 100755 index 00000000..4e88cf17 --- /dev/null +++ b/lib/Tropigate/build_tropigate @@ -0,0 +1,4 @@ +#!/bin/bash +mkdir -p bin +babel --presets es2015,stage-0 -d bin src/ +exit $? \ No newline at end of file diff --git a/lib/Tropigate/install b/lib/Tropigate/install new file mode 100755 index 00000000..377d623f --- /dev/null +++ b/lib/Tropigate/install @@ -0,0 +1,4 @@ +#!/bin/bash +npm install +echo "export PATH=\$PATH:\"$(./scripts/abspath ./)\"" >> ~/.bash_profile +echo "Restart Terminal" \ No newline at end of file diff --git a/lib/Tropigate/package.json b/lib/Tropigate/package.json new file mode 100644 index 00000000..99464184 --- /dev/null +++ b/lib/Tropigate/package.json @@ -0,0 +1,21 @@ +{ + "name": "Tropigate", + "version": "0.0.5", + "description": "", + "main": "bin/main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Blake Loring ", + "license": "ISC", + "devDependencies": {}, + "dependencies": { + "acorn": "^3.3.0", + "escodegen": "^1.8.1", + "babel-cli": "^6.18.0", + "babel-preset-babili": "0.0.9", + "babel-preset-es2015": "^6.3.13", + "babel-preset-stage-0": "^6.3.13" + } +} diff --git a/lib/Tropigate/run_tropigate b/lib/Tropigate/run_tropigate new file mode 100755 index 00000000..51bac7f0 --- /dev/null +++ b/lib/Tropigate/run_tropigate @@ -0,0 +1,2 @@ +#!/bin/bash +node bin/main-cli.js $1 \ No newline at end of file diff --git a/lib/Tropigate/scripts/abspath b/lib/Tropigate/scripts/abspath new file mode 100755 index 00000000..4000c35a --- /dev/null +++ b/lib/Tropigate/scripts/abspath @@ -0,0 +1,3 @@ +#!/bin/bash +cd "$(dirname "$1")" +printf "%s/%s\n" "$(pwd)" "$(basename "$1")" \ No newline at end of file diff --git a/lib/Tropigate/src/Expression.js b/lib/Tropigate/src/Expression.js new file mode 100644 index 00000000..f03bc154 --- /dev/null +++ b/lib/Tropigate/src/Expression.js @@ -0,0 +1,35 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Utils from './Utils'; +import Generator from './Generator'; +import InjectHelper from './InjectHelper'; +import TypeParser from './TypeParser'; + +export default function(acorn, doInjection, instance, opts) { + let tt = acorn.tokTypes; + + function ParseExprAtom(inner) { + return function(refDestructuringErrors) { + if (this.eat(tt.symbolic)) { + let name = this.parseIdent(); + let exprFollows = undefined; + + if (this.eatContextual('initial')) { + exprFollows = this.parseMaybeConditional(true, refDestructuringErrors); + } + + if (doInjection) { + return exprFollows ? Generator.genSymbol.call(this, name, exprFollows) : Generator.genPureSymbol.call(this, name); + } else { + return exprFollows; + } + } else { + return inner.call(this, refDestructuringErrors); + } + } + } + + instance.extend('parseExprAtom', ParseExprAtom); +} diff --git a/lib/Tropigate/src/FunctionSignatures.js b/lib/Tropigate/src/FunctionSignatures.js new file mode 100644 index 00000000..cbd4224f --- /dev/null +++ b/lib/Tropigate/src/FunctionSignatures.js @@ -0,0 +1,58 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Utils from './Utils'; +import Generator from './Generator'; +import InjectHelper from './InjectHelper'; +import TypeParser from './TypeParser'; + +export default function(acorn, doInjection, instance, opts) { + let tt = acorn.tokTypes; + + function ParseFunctionBody(inner) { + return function(node, isArrowFunction) { + let parsed = inner.call(this, node, isArrowFunction); + if (doInjection) { + InjectHelper.injectExpectations.call(this, node); + } + return parsed; + }; + } + + function ParseFunctionParams(inner) { + return function(node) { + inner.call(this, node); + + if (this.eat(tt.typeAnnotation)) { + node.returnType = TypeParser.parseTypeAnnotation.call(this, tt, true); + } + + if (this.eat(tt.where)) { + this._canParseIs = true; + node.whereClause = this.parseExpression(false, {}); + this._canParseIs = false; + } + } + } + + function ParseBindingAtom(inner) { + return function(refDestructuringErrors) { + if (this.type == tt.name) { + let ident = this.parseIdent(); + + if (this.eat(tt.typeAnnotation)) { + ident.expectedType = TypeParser.parseTypeAnnotation.call(this, tt, true); + } + + return ident; + } else { + return inner.call(this, refDestructuringErrors); + } + } + } + + instance.extend('parseFunctionParams', ParseFunctionParams); + instance.extend('parseFunctionBody', ParseFunctionBody); + instance.extend('parseBindingAtom', ParseBindingAtom); +} diff --git a/lib/Tropigate/src/Generator.js b/lib/Tropigate/src/Generator.js new file mode 100644 index 00000000..71ec210a --- /dev/null +++ b/lib/Tropigate/src/Generator.js @@ -0,0 +1,349 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Utils from './Utils'; + +let escodegen = require('escodegen'); + +const SName = '__secret__S$'; +const STName = '__secret__traits__'; +const SymbolFn = 'symbol'; +const PureSymbolFn = 'pureSymbol'; +const SecretRetTemp = '__secret_ret_addr_'; + +function Member(ident, name, identIsExpr, nameIsExpr) { + let S$ = this.startNode() + S$.object = identIsExpr ? ident : Utils.makeIdent.call(this, ident); + S$.property = nameIsExpr ? name : Utils.makeIdent.call(this, name); + S$.computed = false; + return this.finishNode(S$, "MemberExpression"); +} + +function Call(e, argz) { + let call = this.startNode(); + call.callee = e; + call.arguments = argz; + return this.finishNode(call, "CallExpression"); +} + +function MakeS$(name) { + let S$ = this.startNode() + S$.object = Utils.makeIdent.call(this, SName); + S$.property = Utils.makeIdent.call(this, name); + S$.computed = false; + return this.finishNode(S$, "MemberExpression"); +} + +/** + * Stores in __secret__traits__ the given value + */ +function StoreInST(vname, expr) { + let node = this.startNode(); + node.operator = '='; + node.left = Member.call(this, STName, vname, false, true); + node.right = expr; + return this.finishNode(node, "AssignmentExpression"); +} + +function StoreAs(vname, expr) { + let outer = this.startNode(); + outer.kind = 'var'; + + let decl = this.startNode(); + decl.id = vname; + decl.init = expr; + decl = this.finishNode(decl, "VariableDeclarator"); + + outer.declarations = [decl]; + return this.finishNode(outer, "VariableDeclaration"); +} + +function Require(vname, name) { + let call = this.startNode(); + call.callee = Utils.makeIdent.call(this, 'require'); + call.arguments = [Utils.makeIdent.call(this, name)]; + call = this.finishNode(call, "CallExpression"); + + return StoreAs.call(this, Utils.makeIdent.call(this, vname), call); +} + +function WrapT(args) { + let S$ = this.startNode() + S$.object = Utils.makeIdent.call(this, SName); + S$.property = Utils.makeIdent.call(this, 't'); + S$.computed = false; + S$ = this.finishNode(S$, "MemberExpression"); + + let call = this.startNode(); + call.callee = S$; + call.arguments = args; + call = this.finishNode(call, "CallExpression"); + + return call; +} + +function GenPArgs(trait) { + let node = this.startNode(); + node.elements = trait.parameters.map(item => item.ptrait ? item.ptrait : GenTrait.call(this, item)); + return this.finishNode(node, 'ArrayExpression'); +} + +function GenTrait(trait) { + //If the source code marked an expression as a trait (For annotations referencing their own bound traits) + //TODO: Needing to do an if ptrait check twice is ugly + if (trait.ptrait) { + return [trait.ptrait]; + } + + let args = [trait, GenPArgs.call(this, trait)].concat(trait.dependants); + + return WrapT.call(this, args); +} + +function ToLiteral(ident) { + let node = this.startNode(); + node.value = ident.name; + node.raw = ident.name; + return this.finishNode(node, "Literal"); +} + +function WrapArray(items) { + let node = this.startNode(); + node.elements = items; + return this.finishNode(node, 'ArrayExpression'); +} + +function WrapIdentList(list) { + return WrapArray.call(this, list.map(x => ToLiteral.call(this, x))); +} + +function AndExprs(expr1, expr2) { + + if (expr1.value === true) { + return expr2; + } + + let node = this.startNode(); + node.left = expr1; + node.operator = "&&"; + node.right = expr2; + return this.finishNode(node, "LogicalExpression"); +} + +function MkLiteral(val) { + var node = this.startNode(); + node.value = val; + node.raw = '' + val; + return this.finishNode(node, "Literal"); +} + +function GetTop() { + return Member.call(this, SName, 'Top'); +} + +function GenTopT(traitList) { + return WrapT.call(this, [GetTop.call(this), WrapArray.call(this, traitList)]); +} + +function GenerateTraitExprList(traitDefList) { + return traitDefList.map(i => GenTrait.call(this, i)); +} + +/* + * Generate S$.test(expr).is(trait) + */ +function GenTestAtom(expr, traitList) { + return Call.call(this, Member.call(this, Call.call(this, MakeS$.call(this, 'test'), [expr]), 'is', true, false), [GenTopT.call(this, traitList)]); +} + +/** + * GenTest generates type tests in the form x instanceof T && S$.test(x).is(T1) && S$.test(x).is(T2); + */ +function GenTest(expr, type) { + + //type.base should be a typeof + //type.annotations is a set of S$.test.ist + let current = MkLiteral.call(this, true); + + if (type) { + //TODO: Test base type + //TODO: All of these ands can be replaced by a single TopType subtype expr + current = AndExprs.call(this, current, GenTestAtom.call(this, expr, GenerateTraitExprList.call(this, type.annotations))); + } + + return current; +} + +function GenAssert(expr, assertText, assertIsExpr) { + + if (!assertIsExpr) { + //Generate flavor text for assert by stringifying expression + assertText = (assertText || (escodegen.generate(expr) + " failed assertion")); + assertText = Utils.makeIdent.call(this, assertText); + assertText = ToLiteral.call(this, assertText); + } + + let S$ = MakeS$.call(this, 'assert'); + + //TODO: Use the Expr itself to generate the assertion name + let call = this.startNode(); + call.callee = S$; + call.arguments = [expr, assertText]; + return this.finishNode(call, "CallExpression"); +} + +function Add(val1, val2) { + let node = this.startNode(); + node.left = val1; + node.right = val2; + node.operator = "+"; + return this.finishNode(node, "BinaryExpression"); +} + +function GetRider(val) { + return Call.call(this, Member.call(this, SName, 'getRider'), [val]); +} + +function GenExpectationString(val, expected) { + + let result = MkLiteral.call(this, 'Expected ' + escodegen.generate(val) +'('); + result = Add.call(this, result, GetRider.call(this, val)); + result = Add.call(this, result, MkLiteral.call(this, ') to satisfy ')); + + if (expected.base) { + result = Add.call(this, result, MkLiteral.call(this, expected.base)); + } + + //TODO: Replace this section with the generation of the top type and then use that's toString + if (expected.annotations) { + let tr_list = expected.annotations.map(i => GenTrait.call(this, i)); + let top_t = GenTopT.call(this, tr_list); + result = Add.call(this, result, top_t); + } + + return result; +} + +function GenExpectation(val, expected) { + //TODO: We dont generate useful trait assertion messages anymore + return GenAssert.call(this, GenTest.call(this, val, expected), GenExpectationString.call(this, val, expected), true); +} + +function RewriteReturn(returnStmt, retArg, returnType) { + let storeArg = StoreAs.call(this, SecretRetTemp, retArg); + let argument = Utils.makeIdent.call(this, SecretRetTemp); + let expect = GenExpectation.call(this, argument, returnType); + + //Only modify argument if argument is undefined (To avoid weird code generation) + //Example: return; would turn into return undefined; Correct but odd + returnStmt.argument = returnStmt.argument ? argument : undefined; + + let blockReplace = this.startNode(); + blockReplace.body = [storeArg, expect, returnStmt]; + return this.finishNode(blockReplace, "BlockStatement"); +} + +function GenAs(expr, trait) { + let S$ = this.startNode() + S$.object = Utils.makeIdent.call(this, SName); + S$.property = Utils.makeIdent.call(this, 'assume'); + S$.computed = false; + S$ = this.finishNode(S$, "MemberExpression"); + + let call = this.startNode(); + call.callee = S$; + call.arguments = [expr]; + call = this.finishNode(call, "CallExpression"); + + let is = this.startNode(); + is.object = call; + is.property = Utils.makeIdent.call(this, 'is'); + is.computed = false; + is = this.finishNode(is, "MemberExpression"); + + let result = this.startNode(); + result.callee = is; + result.arguments = [GenTopT.call(this, GenerateTraitExprList.call(this, trait.annotations))]; + return this.finishNode(result, 'CallExpression'); +} + +function GenDrop(expr, trait) { + let S$ = this.startNode() + S$.object = Utils.makeIdent.call(this, SName); + S$.property = Utils.makeIdent.call(this, 'assume'); + S$.computed = false; + S$ = this.finishNode(S$, "MemberExpression"); + + let call = this.startNode(); + call.callee = S$; + call.arguments = [expr]; + call = this.finishNode(call, "CallExpression"); + + let is = this.startNode(); + is.object = call; + is.property = Utils.makeIdent.call(this, 'drop'); + is.computed = false; + is = this.finishNode(is, "MemberExpression"); + + let result = this.startNode(); + result.callee = is; + result.arguments = [GenTopT.call(this, GenerateTraitExprList.call(this, trait.annotations))]; + return this.finishNode(result, 'CallExpression'); +} + +export default { + genTraitDef(name, dvals, extender) { + + let methodToCall = Member.call(this, Member.call(this, STName, 'Trait'), 'create', true); + + let newTrait = this.startNode(); + newTrait.callee = methodToCall; + newTrait.arguments = [ToLiteral.call(this, name), WrapIdentList.call(this, dvals)]; + + if (extender) { + newTrait.arguments.push(extender); + } + + newTrait.computed = false; + newTrait = this.finishNode(newTrait, "CallExpression"); + + return StoreInST.call(this, name, newTrait); + }, + genTraitRule(trait, id, fn) { + let methodToCall = Member.call(this, Member.call(this, STName, 'Trait'), 'extend', true); + + let node = this.startNode(); + node.callee = methodToCall; + node.arguments = [trait, ToLiteral.call(this, id), fn]; + node.computed = false; + return this.finishNode(node, "CallExpression"); + }, + rewriteReturn: RewriteReturn, + genExpectation: GenExpectation, + genTraitExpr(name) { + return Member.call(this, STName, name, false, true); + }, + genTest: GenTest, + genSymbol(name, expr) { + let node = this.startNode(); + node.callee = Member.call(this, SName, SymbolFn); + node.arguments = [ToLiteral.call(this, name), expr]; + return this.finishNode(node, 'CallExpression'); + }, + genPureSymbol(name) { + let node = this.startNode(); + node.callee = Member.call(this, SName, PureSymbolFn); + node.arguments = [ToLiteral.call(this, name)]; + return this.finishNode(node, 'CallExpression'); + }, + genAs: GenAs, + genDrop: GenDrop, + genAssumeTrue(expr) { + let S$ = MakeS$.call(this, 'assume'); + let S$Call = Call.call(this, S$, [expr]); + let TrueCall = Member.call(this, S$Call, 'true', true); + return Call.call(this, TrueCall, []); + }, + genAssert: GenAssert +} diff --git a/lib/Tropigate/src/InjectHelper.js b/lib/Tropigate/src/InjectHelper.js new file mode 100644 index 00000000..1cc6815d --- /dev/null +++ b/lib/Tropigate/src/InjectHelper.js @@ -0,0 +1,90 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Utils from './Utils'; +import Generator from './Generator'; + +function InjectPreConditions(node) { + + node.params.forEach(param => { + if (param.expectedType) { + node.body.body.unshift(Utils.wrapStatement.call(this, Generator.genExpectation.call(this, param, param.expectedType))); + } + }); + + if (node.whereClause) { + node.body.body.unshift(Generator.genAssert.call(this, node.whereClause, (node.id.name || "anonymous") + ' where clause failed')); + } +} + +function HandleIf(node, item) { + HandleItem.call(this, node, item.consequent); + if (item.alternate) { + HandleItem.call(this, node, item.alternate); + } +} + +function HandleLoop(node, item) { + HandleItem.call(this, node, item.body); +} + +function HandleItem(node, item) { + + //TODO: This list isn't exhaustive, find a way to make ExpressionStatements and BlockStatements work + if (item.type === "BlockStatement") { + HandleBlock.call(this, node, item); + } else if (item.type === "ForStatement" || item.type === "WhileStatement") { + HandleLoop.call(this, node, item); + } else if (item.type === "IfStatement") { + HandleIf.call(this, node, item); + } else { + //console.log('ERROR - Unhandled Type of node in injection, possible code generation error'); + } +} + +function HandleBlock(node, item) { + item.body.forEach(item => HandleItem.bind(this, node)); + RecursiveInjectBlock.call(this, item); +} + +function WholeInject(node) { + HandleItem.call(this, node, node.body); +} + +function RetInject(node, retExpr) { + let argument = retExpr.argument ? retExpr.argument : Utils.makeIdent.call(this, 'undefined'); + return Generator.rewriteReturn(retExpr, argument, node.returnType); +} + +function RecursiveInjectBlock(node) { + let relevent = node.body; + let nextIdx = relevent.findIndex(item => !item._typeInjectionDone && item.type === "ReturnStatement"); + if (nextIdx != -1) { + if (node.returnType) { + relevent[nextIdx] = RetInject.call(this, node, relevent[nextIdx]); + } + + relevent[nextIdx]._typeInjectionDone = true; + RecursiveInjectBlock.call(this, node); + + return; + } +} + +function InjectPostConditions(node, relevent) { + WholeInject.call(this, node); + if (node.returnType) { + node.body.body.push(Utils.wrapStatement.call(this, + Generator.genExpectation.call(this, Utils.makeIdent.call(this, 'undefined'), node.returnType))); + } +} + +function InjectExpectations(node) { + InjectPreConditions.call(this, node); + InjectPostConditions.call(this, node); +} + +export default { + injectExpectations: InjectExpectations +} diff --git a/lib/Tropigate/src/Prelude.js b/lib/Tropigate/src/Prelude.js new file mode 100644 index 00000000..bd950940 --- /dev/null +++ b/lib/Tropigate/src/Prelude.js @@ -0,0 +1,25 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +export default function(src, doInjection) { + + function includes(search, start) { + 'use strict'; + if (typeof start !== 'number') { + start = 0; + } + + if (start + search.length > this.length) { + return false; + } else { + return this.indexOf(search, start) !== -1; + } + }; + + if (!includes.call(src, '"no_prelude";')) { + src = "var __secret__S$ = require(\'S$\'); var __secret__traits__ = __secret__S$.Traits;\n" + src; + } + + return src; +} diff --git a/lib/Tropigate/src/Statements.js b/lib/Tropigate/src/Statements.js new file mode 100644 index 00000000..ed8983ad --- /dev/null +++ b/lib/Tropigate/src/Statements.js @@ -0,0 +1,111 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Utils from './Utils'; +import Generator from './Generator'; +import InjectHelper from './InjectHelper'; +import TypeParser from './TypeParser'; + +export default function(acorn, doInjection, instance, opts) { + let tt = acorn.tokTypes; + + function ParseTraitDependants() { + let names = []; + + do { + names.push(this.parseIdent()); + } while (this.eat(tt.comma)); + + this.expect(tt.parenR); + + return names; + } + + function ParseTraitParams() { + let dvals = []; + + if (this.eat(tt.parenL)) { + dvals = ParseTraitDependants.call(this); + } + + return dvals; + } + + function ParseTraitDef() { + let name = this.parseIdent(); + let dvals = ParseTraitParams.call(this); + let extender; + + if (this.eat(tt._extends)) { + extender = Generator.genTraitExpr.call(this, this.parseIdent()); + } + + this.semicolon(); + + if (doInjection) { + return Utils.wrapStatement.call(this, Generator.genTraitDef.call(this, name, dvals, extender)); + } else { + return this.finishNode(this.startNode(), "EmptyStatement"); + } + } + + function ParseTraitRule() { + let traitPath = this.parseExpression(false, {}); + let toOverride = this.parseIdent(); + let fnOveride = this.startNode(); + this.parseFunction(fnOveride, false); + this.semicolon(); + + if (doInjection) { + return Utils.wrapStatement.call(this, Generator.genTraitRule.call(this, traitPath, toOverride, fnOveride)); + } else { + return this.finishNode(this.startNode(), "EmptyStatement"); + } + } + + function ParseAssert() { + this._canParseIs = true; + let result = Utils.wrapStatement.call(this, Generator.genAssert.call(this, this.parseExpression(false, {}))); + this._canParseIs = false; + + this.semicolon(); + + if (doInjection) { + return result; + } else { + return this.finishNode(this.startNode(), "EmptyStatement"); + } + } + + function ParseAssume() { + let assumption = this.parseExpression(false, {}); + let result = Utils.wrapStatement.call(this, Generator.genAssumeTrue.call(this, assumption)); + + this.semicolon(); + + if (doInjection) { + return result; + } else { + return this.finishNode(this.startNode(), "EmptyStatement"); + } + } + + function ParseStatement(inner) { + return function(declaration, topLevel, exports) { + if (this.eat(tt.traitdef)) { + return ParseTraitDef.call(this); + } else if (this.eat(tt.traitrule)) { + return ParseTraitRule.call(this); + } else if (this.eat(tt.assert)) { + return ParseAssert.call(this); + } else if (this.eat(tt.assume)) { + return ParseAssume.call(this); + } else { + return inner.call(this, declaration, topLevel, exports); + } + } + } + + instance.extend('parseStatement', ParseStatement); +} diff --git a/lib/Tropigate/src/Tokens.js b/lib/Tropigate/src/Tokens.js new file mode 100644 index 00000000..8b9b2b79 --- /dev/null +++ b/lib/Tropigate/src/Tokens.js @@ -0,0 +1,125 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +export default function(acorn, inject, instance, opts) { + let tt = acorn.tokTypes; + let tc = acorn.tokContexts; + + tt.typeAnnotation = tt.colon; + tt.startTrait = new acorn.TokenType("", {}); + + // Succinct definitions of keyword token types + function kw(name, options = {}) { + options.keyword = name + return new acorn.TokenType(name, options) + } + + function binop(name, kw, prec) { + let options = { + beforeExpr: false, + binop: prec, + keyword: kw + }; + return new acorn.TokenType(name, options); + } + + tt.assert = kw('assert'); + tt.assume = kw('assume'); + tt.where = kw('where'); + tt.traitdef = kw('traitdef'); + tt.traitrule = kw('traitrule'); + + tt.symbolic = new acorn.TokenType("symbolic", { + startsExpr: true + }); + + tt.ptrait = kw('ptrait'); + + tt.is = new acorn.TokenType("is", { + postfix: true, + startsExpr: true + }); + + tt.drop = new acorn.TokenType("drop", { + postfix: true, + startsExpr: true + }); + + tt.as = new acorn.TokenType("as", { + postfix: true, + startsExpr: true + }); + + function ReadToken(inner) { + return function(code) { + + if (this.input.slice(this.pos, this.pos + 7) === 'assert ') { + this.pos += 7; + return this.finishToken(tt.assert); + } + + if (this.input.slice(this.pos, this.pos + 7) === 'assume ') { + this.pos += 7; + return this.finishToken(tt.assume); + } + + if (this.input.slice(this.pos, this.pos + 5) === 'drop ') { + this.pos += 5; + return this.finishToken(tt.drop); + } + + if (this.input.slice(this.pos, this.pos + 6) === 'where ') { + this.pos += 6; + return this.finishToken(tt.where); + } + + if (this.input.slice(this.pos, this.pos + 9) === 'symbolic ') { + this.pos += 9; + return this.finishToken(tt.symbolic); + } + + if (this.input.slice(this.pos, this.pos + 9) === 'traitdef ') { + this.pos += 9; + return this.finishToken(tt.traitdef); + } + + if (this.input.slice(this.pos, this.pos + 10) === 'traitrule ') { + this.pos += 10; + return this.finishToken(tt.traitrule); + } + + if (this.input.slice(this.pos, this.pos + 7) === 'ptrait ') { + this.pos += 7; + return this.finishToken(tt.ptrait); + } + + if (this.input.slice(this.pos, this.pos + 3) == 'is ') { + this.pos += 2; + return this.finishToken(tt.is); + } + + if (this.input.slice(this.pos, this.pos + 3) == 'as ') { + this.pos += 2; + return this.finishToken(tt.as); + } + + if (this.input.slice(this.pos, this.pos + 2) == '') { + this.pos += 2; + return this.finishToken(tt.endTrait); + } + + return inner.call(this, code); + } + } + + instance.extend('readToken', ReadToken); + + return tt; +} diff --git a/lib/Tropigate/src/Tropigate.js b/lib/Tropigate/src/Tropigate.js new file mode 100644 index 00000000..a23bdcff --- /dev/null +++ b/lib/Tropigate/src/Tropigate.js @@ -0,0 +1,19 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Tokens from './Tokens'; +import Unary from './Unary'; +import FunctionSignatures from './FunctionSignatures'; +import Statements from './Statements'; +import Expression from './Expression'; + +export default function(acorn, doInjection) { + acorn.plugins.tropigate = function(instance, opts) { + Tokens(acorn, doInjection, instance, opts); + Unary(acorn, doInjection, instance, opts); + FunctionSignatures(acorn, doInjection, instance, opts); + Statements(acorn, doInjection, instance, opts); + Expression(acorn, doInjection, instance, opts); + } +}; diff --git a/lib/Tropigate/src/TypeParser.js b/lib/Tropigate/src/TypeParser.js new file mode 100644 index 00000000..c2efe2fb --- /dev/null +++ b/lib/Tropigate/src/TypeParser.js @@ -0,0 +1,77 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Generator from './Generator'; + +function StartTrait(tt) { + return this.type == tt.startTrait; +} + +function ParseTrait(tt) { + + let startPos = this.start, + startLoc = this.startLoc; + + let annotation = Generator.genTraitExpr.call(this, this.parseIdent()); + annotation.parameters = []; + annotation.dependants = []; + + if (StartTrait.call(this, tt)) { + this.next(); + annotation.parameters = ParseTraitList.call(this, tt); + } + + if (this.eat(tt.parenL)) { + annotation.dependants = this.parseExprList(tt.parenR, true, false, {}); + } + + return annotation; +} + +function ParseTraitList(tt) { + let traitList = []; + + do { + if (this.eat(tt.ptrait)) { + traitList.push({ + ptrait: this.parseSubscripts(this.parseExprAtom({}), this.start, this.startLoc, true, {}) + }); + } else { + traitList.push(ParseTrait.call(this, tt)); + } + } while (this.eat(tt.star)); + + this.expect(tt.endTrait); + + return traitList; +} + +function ParseTypeAnnotation(tt, allowBaseType) { + + if (!StartTrait.call(this, tt) && this.type !== tt.name) { + this.unexpected(); + } + + let base, + annotations = [], + whereClause; + + if (allowBaseType && this.type == tt.name) { + base = this.parseExpression(true, {}); + } + + if (this.eat(tt.startTrait)) { + annotations = ParseTraitList.call(this, tt); + } + + return { + base: base, + annotations: annotations, + whereClause: whereClause + }; +} + +export default { + parseTypeAnnotation: ParseTypeAnnotation +} diff --git a/lib/Tropigate/src/Unary.js b/lib/Tropigate/src/Unary.js new file mode 100644 index 00000000..c1df244e --- /dev/null +++ b/lib/Tropigate/src/Unary.js @@ -0,0 +1,81 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import Utils from './Utils'; +import Generator from './Generator'; +import InjectHelper from './InjectHelper'; +import TypeParser from './TypeParser'; + +function BuildTraitWrapper(doInjection, tt, expr, callback) { + this.next(); + + let annotation = TypeParser.parseTypeAnnotation.call(this, tt, false); + + //Replace EXPR with assume if Injection + if (doInjection) { + expr = callback.call(this, expr, annotation); + } + + return expr; +} + +export default function(acorn, doInjection, instance, opts) { + let tt = acorn.tokTypes; + + //TODO: Push upstream change to acorn so I dont have to replace alllll of this crazyness + function ParseMaybeUnary(inner) { + return function(refDestructuringErrors, sawUnary) { + + let startPos = this.start, + startLoc = this.startLoc, + expr; + + if (this.inAsync && this.isContextual("await")) { + expr = this.parseAwait(refDestructuringErrors) + sawUnary = true + } else if (this.type.prefix) { + let node = this.startNode(), + update = this.type === tt.incDec + node.operator = this.value + node.prefix = true + this.next() + node.argument = this.parseMaybeUnary(refDestructuringErrors, true) + this.checkExpressionErrors(refDestructuringErrors, true) + if (update) this.checkLVal(node.argument) + else if (this.strict && node.operator === "delete" && + node.argument.type === "Identifier") + this.raiseRecoverable(node.start, "Deleting local variable in strict mode") + else sawUnary = true + expr = this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression") + } else { + expr = this.parseExprSubscripts(refDestructuringErrors) + if (this.checkExpressionErrors(refDestructuringErrors)) return expr + while (this.type.postfix && !this.canInsertSemicolon()) { + if (this.type == tt.as) { + expr = BuildTraitWrapper.call(this, doInjection, tt, expr, Generator.genAs); + } else if (this.type == tt.drop) { + expr = BuildTraitWrapper.call(this, doInjectation, tt, expr, Generator.genDrop); + } else if (this._canParseIs && this.type == tt.is) { + expr = BuildTraitWrapper.call(this, doInjection, tt, expr, Generator.genTest); + } else { + let node = this.startNodeAt(startPos, startLoc) + node.operator = this.value + node.prefix = false + node.argument = expr + this.checkLVal(expr) + this.next() + expr = this.finishNode(node, "UpdateExpression") + } + } + } + + if (!sawUnary && this.eat(tt.starstar)) + return this.buildBinary(startPos, startLoc, expr, this.parseMaybeUnary(refDestructuringErrors, false), "**", false) + else + return expr + }; + } + + instance.extend('parseMaybeUnary', ParseMaybeUnary); +} diff --git a/lib/Tropigate/src/Utils.js b/lib/Tropigate/src/Utils.js new file mode 100755 index 00000000..9157e776 --- /dev/null +++ b/lib/Tropigate/src/Utils.js @@ -0,0 +1,16 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +export default { + makeIdent(name) { + let node = this.startNode(); + node.name = name; + return this.finishNode(node, 'Identifier'); + }, + wrapStatement(expr) { + let node = this.startNode(); + node.expression = expr; + return this.finishNode(node, 'ExpressionStatement'); + } +} diff --git a/lib/Tropigate/src/main-cli.js b/lib/Tropigate/src/main-cli.js new file mode 100644 index 00000000..1ef7058e --- /dev/null +++ b/lib/Tropigate/src/main-cli.js @@ -0,0 +1,21 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import main from './main'; +import fs from 'fs'; + +function parseFile(filename, call) { + fs.readFile(filename, function(err, data) { + + if (err) { + throw err; + } + + var src = data.toString(); + call(main(src)); + }); +} + +let fileName = process.argv[2]; +parseFile(fileName, code => console.log(code)); diff --git a/lib/Tropigate/src/main.js b/lib/Tropigate/src/main.js new file mode 100644 index 00000000..baf85795 --- /dev/null +++ b/lib/Tropigate/src/main.js @@ -0,0 +1,44 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +import tropigate from './Tropigate'; +import Prelude from './Prelude'; + +let acorn = require('acorn'); +let escodegen = require('escodegen'); + +function dEnv(field, dVal) { + return process.env[field] !== undefined ? process.env[field] : dVal; +} + +let doInjection = dEnv('TROP_DO_INJECT', 'YES') != 'NO'; + +//Inject tropigate +tropigate(acorn, doInjection); + +function convert(src, opts, shouldCommentOut) { + + let comments = [], + tokens = []; + + opts.plugins = { + tropigate: true + }; + + opts.onComment = comments; + opts.onToken = tokens; + + //Use plugin to transform the source + let ast = acorn.parse(Prelude(src, doInjection), opts); + + if (shouldCommentOut) { + escodegen.attachComments(ast, comments, tokens); + } + + return escodegen.generate(ast, { + comments: true + }); +} + +export default convert; diff --git a/lib/Tropigate/tropigate b/lib/Tropigate/tropigate new file mode 100755 index 00000000..5f9b79cd --- /dev/null +++ b/lib/Tropigate/tropigate @@ -0,0 +1,23 @@ +#!/bin/bash + +#Store the current working directory to return after calculating abspath +ORIGIN_PATH="$(pwd)" + +#Generate the absolute path of test script as it will be lost on cd into Tropigate +cd "$(dirname "$1")" +TARGET_REAL_PATH="$(printf "%s/%s\n" "$(pwd)" "$(basename "$1")")" +OUT_REAL_PATH="$(printf "%s/%s\n" "$(pwd)" "$(basename "$2")")" + +#cd to Tropigate directory +cd $ORIGIN_PATH +cd "$(dirname "${BASH_SOURCE[0]}")" + +if [ -z ${NO_COMPILE} ]; then + ./build_tropigate +fi + +if [ -z ${2} ]; then + ./run_tropigate $TARGET_REAL_PATH +else + ./run_tropigate $TARGET_REAL_PATH > $OUT_REAL_PATH +fi \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..e08f7f2b --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "expose", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "author": "Blake Loring", + "scripts": { + "start": "export PATH=$PATH:$(pwd) && cd Dashboard && npm start", + "postinstall": "./expoSE setup" + } +} diff --git a/scripts/abspath b/scripts/abspath new file mode 100755 index 00000000..f6238c04 --- /dev/null +++ b/scripts/abspath @@ -0,0 +1,3 @@ +#!/bin/bash -e +cd "$(dirname "$1")" +printf "%s/%s\n" "$(pwd)" "$(basename "$1")" \ No newline at end of file diff --git a/scripts/analyse b/scripts/analyse new file mode 100755 index 00000000..6914aa3b --- /dev/null +++ b/scripts/analyse @@ -0,0 +1,10 @@ +#!/bin/bash -e + +#Setup runtime environment variables +source ./scripts/expose_env + +#Generate the real path to script if it is relative +TARGET_REAL_PATH="$(./scripts/abspath $1)" + +#Invoke analyser +(node Distributor/bin/Distributor.js $TARGET_REAL_PATH ${@:2}) \ No newline at end of file diff --git a/scripts/build/build b/scripts/build/build new file mode 100755 index 00000000..e944d13a --- /dev/null +++ b/scripts/build/build @@ -0,0 +1,6 @@ +#!/bin/bash -e + +cd $1 +mkdir -p $3 +node node_modules/babel-cli/bin/babel --presets es2015,stage-0 -d $3 $2/ +exit $? \ No newline at end of file diff --git a/scripts/build/build_analyser b/scripts/build/build_analyser new file mode 100755 index 00000000..cc20a4f0 --- /dev/null +++ b/scripts/build/build_analyser @@ -0,0 +1,27 @@ +#!/bin/bash -e + +./scripts/build/build_libs + +if [ $? -ne 0 ]; then + exit 1 +fi + +#Set the log level to 1 if it isn't already set +if [ -z ${EXPOSE_LOG_LEVEL+x} ]; then + EXPOSE_LOG_LEVEL="1" +fi + +#Build the thread distributor +(./scripts/build/build Distributor src bin) + +#Build the actual analyser +(EXPOSE_LOG_LEVEL=$EXPOSE_LOG_LEVEL ./scripts/build/strip Analyser src bin) +(./scripts/build/build Analyser bin bin) +(./scripts/build/bundle Analyser "bin/Analyser.js" "bin/bundle.js") + +if [ $? -ne "0" ]; then + echo "Analyser failed to build" + exit 1 +fi + +exit $? \ No newline at end of file diff --git a/scripts/build/build_libs b/scripts/build/build_libs new file mode 100755 index 00000000..d79885a8 --- /dev/null +++ b/scripts/build/build_libs @@ -0,0 +1,31 @@ +#!/bin/bash -e + +#(./scripts/build/build lib/Annotations src bin) + +#if [ $? -ne 0 ]; then +# echo "Annotations failed to build" +# exit 1 +#fi + +(./scripts/build/build lib/Mocker src bin) + +if [ $? -ne 0 ]; then + echo "Mocker failed to build" + exit 1 +fi + +(./scripts/build/build lib/S$ src bin) + +if [ $? -ne 0 ]; then + echo "S$ failed to build" + exit 1 +fi + +(./scripts/build/build lib/Tropigate src bin) + +if [ $? -ne 0 ]; then + echo "Tropigate failed to build" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/scripts/build/build_strip_rc b/scripts/build/build_strip_rc new file mode 100755 index 00000000..380fb972 --- /dev/null +++ b/scripts/build/build_strip_rc @@ -0,0 +1,11 @@ +#!/bin/bash -e + +if [ $EXPOSE_LOG_LEVEL -lt 1 ]; then + echo "{\"plugins\":[[\"strip-function-call\",{\"strip\": [\"Log.logHigh\", \"Log.logMid\", \"Log.log\"]}]]}" +elif [ $EXPOSE_LOG_LEVEL -lt 2 ]; then + echo "{\"plugins\":[[\"strip-function-call\",{\"strip\": [\"Log.logHigh\", \"Log.logMid\"]}]]}" +elif [ $EXPOSE_LOG_LEVEL -lt 3 ]; then + echo "{\"plugins\":[[\"strip-function-call\",{\"strip\": [\"Log.logHigh\"]}]]}" +else + echo "{}" +fi \ No newline at end of file diff --git a/scripts/build/build_tester b/scripts/build/build_tester new file mode 100755 index 00000000..a71a509c --- /dev/null +++ b/scripts/build/build_tester @@ -0,0 +1,8 @@ +#!/bin/bash -e + +(./scripts/build/build Tester src bin) + +if [ $? -ne "0" ]; then + echo "Tester failed to build" + exit 1 +fi \ No newline at end of file diff --git a/scripts/build/bundle b/scripts/build/bundle new file mode 100755 index 00000000..2e33f0f5 --- /dev/null +++ b/scripts/build/bundle @@ -0,0 +1,6 @@ +#!/bin/bash +cd $1 +echo "Bundling $1/$2 into $3" + +#TODO: Refactor out all these hardcoded node modules paths +./node_modules/browserify/bin/cmd.js -u=z3javascript -u=process -u=electron --node $2 --outfile $3 \ No newline at end of file diff --git a/scripts/build/strip b/scripts/build/strip new file mode 100755 index 00000000..13ca62b2 --- /dev/null +++ b/scripts/build/strip @@ -0,0 +1,8 @@ +#!/bin/bash -e + +(EXPOSE_LOG_LEVEL=$EXPOSE_LOG_LEVEL ./scripts/build/build_strip_rc) > $1/.babelrc +cd $1 +mkdir -p $3 +node node_modules/babel-cli/bin/babel -d $3 $2/ +rm .babelrc +exit $? \ No newline at end of file diff --git a/scripts/expose_env b/scripts/expose_env new file mode 100755 index 00000000..4ec143af --- /dev/null +++ b/scripts/expose_env @@ -0,0 +1,14 @@ +#!/bin/bash -e + +#Node path should add libs in +export NODE_PATH=$NODE_PATH:"$(./scripts/lib_path)" + +#Z3_PATH dictates where z3_js will look for Z3 in +if [ -e ${Z3_PATH+x} ]; then + if [[ "$OSTYPE" == "darwin"* ]]; then + export Z3_PATH="./node_modules/z3javascript/bin/libz3.dylib" + else + export Z3_PATH="./node_modules/z3javascript/bin/libz3.so" + fi + echo "Set Default Z3_PATH to $Z3_PATH" +fi \ No newline at end of file diff --git a/scripts/lib_path b/scripts/lib_path new file mode 100755 index 00000000..62e92cb9 --- /dev/null +++ b/scripts/lib_path @@ -0,0 +1,2 @@ +#!/bin/bash -e +echo "$(cd lib; pwd)/$(basename "$1")" \ No newline at end of file diff --git a/scripts/license_all b/scripts/license_all new file mode 100755 index 00000000..2518c871 --- /dev/null +++ b/scripts/license_all @@ -0,0 +1,2 @@ +#!/bin/bash +find ./ -path *node_modules/ -prune -o -type f -name "*.js" -exec ./scripts/relicense {} \; \ No newline at end of file diff --git a/scripts/play b/scripts/play new file mode 100755 index 00000000..fbb787a4 --- /dev/null +++ b/scripts/play @@ -0,0 +1,6 @@ +#!/bin/bash -e +if [ -z ${DEBUG} ]; then + (cd Analyser && node ./node_modules/jalangi2/src/js/commands/jalangi.js --inlineIID --inlineSource --analysis bin/bundle.js "${@:1}") +else + (cd Analyser && node --inspect --debug-brk ./node_modules/jalangi2/src/js/commands/jalangi.js --inlineIID --inlineSource --analysis bin/bundle.js "${@:1}") +fi \ No newline at end of file diff --git a/scripts/pre-push b/scripts/pre-push new file mode 100755 index 00000000..4ecd5c04 --- /dev/null +++ b/scripts/pre-push @@ -0,0 +1,3 @@ +#!/bin/bash -e +./expoSE test_suite +exit $? \ No newline at end of file diff --git a/scripts/relicense b/scripts/relicense new file mode 100755 index 00000000..b3b49d11 --- /dev/null +++ b/scripts/relicense @@ -0,0 +1,4 @@ +#!/bin/bash +grep -q '\/\*.*(c).*\*\/' $1 || echo "/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +$(cat $1)" > $1 \ No newline at end of file diff --git a/scripts/run_tests b/scripts/run_tests new file mode 100755 index 00000000..41ded877 --- /dev/null +++ b/scripts/run_tests @@ -0,0 +1,15 @@ +#!/bin/bash -e +./scripts/build/build_tester + +if [ $? -ne 0 ]; then + exit 1 +fi + +if [ -z $TESTS_MAX_CONCURRENT ]; then + export TESTS_MAX_CONCURRENT=30 +fi + +node --version +node ./Tester/bin/TestRunner.js --concurrent "$TESTS_MAX_CONCURRENT" ./tests/ + +exit $? \ No newline at end of file diff --git a/scripts/setup/cleanup b/scripts/setup/cleanup new file mode 100755 index 00000000..e4c8035a --- /dev/null +++ b/scripts/setup/cleanup @@ -0,0 +1,4 @@ +#!/bin/bash -e +echo "Cleaning $1" +cd $1 +rm -rf node_modules \ No newline at end of file diff --git a/scripts/setup/cleanup_bashprofile b/scripts/setup/cleanup_bashprofile new file mode 100755 index 00000000..d043b2e4 --- /dev/null +++ b/scripts/setup/cleanup_bashprofile @@ -0,0 +1,4 @@ +#!/bin/bash -e +mv ~/.bash_profile ~/.bash_profile_exp_tmp +sed "s/.*#EXPOSE_ENTRY//g" < ~/.bash_profile_exp_tmp > ~/.bash_profile +rm ~/.bash_profile_exp_tmp \ No newline at end of file diff --git a/scripts/setup/install_bashprofile b/scripts/setup/install_bashprofile new file mode 100755 index 00000000..46e7e5e1 --- /dev/null +++ b/scripts/setup/install_bashprofile @@ -0,0 +1,3 @@ +#!/bin/bash -e +echo "export EXPOSE_PATH=\"$(./scripts/abspath ./)\" #EXPOSE_ENTRY" >> ~/.bash_profile +echo "export PATH=\$PATH:\$EXPOSE_PATH" "#EXPOSE_ENTRY" >> ~/.bash_profile \ No newline at end of file diff --git a/scripts/setup/setup b/scripts/setup/setup new file mode 100755 index 00000000..32181956 --- /dev/null +++ b/scripts/setup/setup @@ -0,0 +1,23 @@ +#!/bin/bash -e + +echo "Setting up for Node" +node --version + +echo "Installing pre-commit" +cp ./scripts/pre-push .git/hooks/ + +echo "Setting Up Packages" +./scripts/setup/setup_packages + +touch ~/.bash_profile + +echo "Cleaning previous installations" +./scripts/setup/cleanup_bashprofile + +echo "Installing into PATH" +./scripts/setup/install_bashprofile + +echo "Re-sourcing bash profile" +source ~/.bash_profile + +exit 0 \ No newline at end of file diff --git a/scripts/setup/setup_packages b/scripts/setup/setup_packages new file mode 100755 index 00000000..a66e45d9 --- /dev/null +++ b/scripts/setup/setup_packages @@ -0,0 +1,8 @@ +#!/bin/bash -e +(./scripts/setup/setup_pkg lib/Mocker mocker.json) +(./scripts/setup/setup_pkg lib/S$ symbols.json) +(./scripts/setup/setup_pkg lib/Tropigate tropigate.json) +(./scripts/setup/setup_pkg Analyser analyser.json) +(./scripts/setup/setup_pkg Tester tester.json) +(./scripts/setup/setup_pkg Distributor distributor.json) +(./scripts/setup/setup_pkg Dashboard) \ No newline at end of file diff --git a/scripts/setup/setup_pkg b/scripts/setup/setup_pkg new file mode 100755 index 00000000..a109dfe2 --- /dev/null +++ b/scripts/setup/setup_pkg @@ -0,0 +1,10 @@ +#!/bin/bash -e +if [ -z ${NO_CLEANUP} ]; then + ./scripts/setup/cleanup $1 +fi + +echo "Setting up $1" + +cd $1 +echo "Pulling Dependancies" +CC=clang CXX=clang++ npm install \ No newline at end of file diff --git a/tests/assert/assert.js b/tests/assert/assert.js new file mode 100644 index 00000000..43bcbddf --- /dev/null +++ b/tests/assert/assert.js @@ -0,0 +1,12 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; +var x = symbolic X initial 0; +assume x < 4; + +for (var i = 0; i < x; i++) { + console.log("Iteration " + i); +} + +assert i != 5; +console.log("Assertion did not fail"); diff --git a/tests/assert/fail_as.js b/tests/assert/fail_as.js new file mode 100644 index 00000000..1311f124 --- /dev/null +++ b/tests/assert/fail_as.js @@ -0,0 +1,14 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +traitdef Test; +traitdef NoTest; + +var x = 5 as drop ; + +function f(x: ) { + +} + +f(x); + +assert x is ; diff --git a/tests/assumes/basic.js b/tests/assumes/basic.js new file mode 100644 index 00000000..cc63e64f --- /dev/null +++ b/tests/assumes/basic.js @@ -0,0 +1,7 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +var QR = symbolic QR initial false; +var QK = symbolic QK initial false; + +assume QR === QK; +assert QR === QK; diff --git a/tests/assumes/wrapped_assume.js b/tests/assumes/wrapped_assume.js new file mode 100644 index 00000000..4c6e5e2c --- /dev/null +++ b/tests/assumes/wrapped_assume.js @@ -0,0 +1,5 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +var x = symbolic X initial false; +assume x === true; +assert x === true; diff --git a/tests/async/settimeout.js b/tests/async/settimeout.js new file mode 100644 index 00000000..1cb4ca13 --- /dev/null +++ b/tests/async/settimeout.js @@ -0,0 +1,18 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +setTimeout(function() { + var x = symbolic A initial 5; + + if (x > 10) { + console.log('X > 10'); + setTimeout(function() { + if (x < 20) { + console.log('Err'); + throw 'AAAH'; + } + }, 200); + } else { + console.log('X <= 10'); + } + +}, 150); diff --git a/tests/bool/basic.js b/tests/bool/basic.js new file mode 100644 index 00000000..a9115352 --- /dev/null +++ b/tests/bool/basic.js @@ -0,0 +1,15 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var A = symbolic A initial true; +var B = symbolic B initial true; + +if (A) { + if (B) { + assert false; + } +} else { + if (B) { + } +} diff --git a/tests/bool/hello.js b/tests/bool/hello.js new file mode 100644 index 00000000..68feac26 --- /dev/null +++ b/tests/bool/hello.js @@ -0,0 +1,19 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +console.log('Start'); + +var q = symbolic Q initial true; + +console.log('Create'); + +if (!q) { + assert !q; +} + +if (q) { + assert q; +} + +console.log('Path Finished'); diff --git a/tests/core/bools.js b/tests/core/bools.js new file mode 100644 index 00000000..4fdb2f07 --- /dev/null +++ b/tests/core/bools.js @@ -0,0 +1,21 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +var a; +var b; + +a = true; +b = a; + +assert a == b; + +a = !a; + +assert a != b; + +b = !a; + +assert b != a; + +b = a; + +assert b == a; diff --git a/tests/core/lamda.js b/tests/core/lamda.js new file mode 100644 index 00000000..266e726b --- /dev/null +++ b/tests/core/lamda.js @@ -0,0 +1,6 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +var x = function() { return x.y; }; +var y = function() {}; +x.y = y; +x()(); diff --git a/tests/core/recursion.js b/tests/core/recursion.js new file mode 100644 index 00000000..983caa99 --- /dev/null +++ b/tests/core/recursion.js @@ -0,0 +1,12 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +function fibs(n) { + + if (n < 2) { + return n; + } + + return fibs(n - 1) + fibs(n - 2); +} + +fibs(15); diff --git a/tests/fractions/fraction_one.js b/tests/fractions/fraction_one.js new file mode 100644 index 00000000..d96265b5 --- /dev/null +++ b/tests/fractions/fraction_one.js @@ -0,0 +1,14 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +console.log('Fraction Test'); +console.log('Loading Symbols'); + +var x = symbolic X initial 0; + +console.log('Made X'); + +if (x > 0 && x < 1) { + console.log("Bla"); +} diff --git a/tests/integers/breaker.js b/tests/integers/breaker.js new file mode 100644 index 00000000..9ebaf98c --- /dev/null +++ b/tests/integers/breaker.js @@ -0,0 +1,6 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var loInput = symbolic LO initial 0; +var result = loInput * 42; diff --git a/tests/integers/coerce.js b/tests/integers/coerce.js new file mode 100644 index 00000000..b7c7686c --- /dev/null +++ b/tests/integers/coerce.js @@ -0,0 +1,7 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var x = symbolic X initial 10; + +if (x) {} else {} diff --git a/tests/integers/hello.js b/tests/integers/hello.js new file mode 100644 index 00000000..7833af8f --- /dev/null +++ b/tests/integers/hello.js @@ -0,0 +1,15 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var x = symbolic X initial 5; +var a = symbolic A initial true; + +console.log("x is initialized to", x); +console.log("a is initialized to", a); + +if (x > 0) { + assert x > 0; +} else { + assert x <= 0; +} diff --git a/tests/integers/infoflow.js b/tests/integers/infoflow.js new file mode 100644 index 00000000..63cfbe19 --- /dev/null +++ b/tests/integers/infoflow.js @@ -0,0 +1,46 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +function flowTest(lo, hi) { + + console.log("Inputs: Hi:", hi, "Lo:", lo); + + var result = lo * 42; + + if (lo > 4) { + console.log("Branch A-then"); + result -= lo; + } else { + console.log("Branch A-else"); + if (hi == 777) { + result = -result; + } + } + + if (hi > 0) { + console.log("Branch B-then"); + } else { + console.log("Branch B-else"); + } + + console.log("Low output:", result); + + return result; +} + +function verify(f) { + + var loInput = symbolic LO initial 0; + var hiInput1 = symbolic HI1 initial 10; + var hiInput2 = symbolic HI2 initial 10; + + var loOutput1 = f(loInput, hiInput1); + var loOutput2 = f(loInput, hiInput2); + + if (hiInput1 !== 777 && hiInput2 !== 777) { + assert loOutput1 === loOutput2;; + } +} + +verify(flowTest); diff --git a/tests/integers/lt.js b/tests/integers/lt.js new file mode 100644 index 00000000..eae5d81a --- /dev/null +++ b/tests/integers/lt.js @@ -0,0 +1,11 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var x = symbolic X initial 10; + +if (x < 150) { + if (x > 75) { + console.log('Bye'); + } +} diff --git a/tests/integers/mul.js b/tests/integers/mul.js new file mode 100644 index 00000000..06d61d5e --- /dev/null +++ b/tests/integers/mul.js @@ -0,0 +1,9 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var x = symbolic X initial 10; + +if (x * 100 > 150) { + assert x * 100 > 150; +} diff --git a/tests/loops/loop_alot.js b/tests/loops/loop_alot.js new file mode 100644 index 00000000..83f865ce --- /dev/null +++ b/tests/loops/loop_alot.js @@ -0,0 +1,13 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +var q = symbolic Q initial 10; + +if (q < 10) { + var j = 0; + + for (var i = 0; i < q; i++) { + j++; + } + + console.log('Done ' + j); +} diff --git a/tests/named_method/simple.js b/tests/named_method/simple.js new file mode 100644 index 00000000..910c7ab4 --- /dev/null +++ b/tests/named_method/simple.js @@ -0,0 +1,9 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +var me = {}; + +me.q = function hi() { + console.log('Hi'); +} + +me.q(); diff --git a/tests/numbers/floor_check.js b/tests/numbers/floor_check.js new file mode 100644 index 00000000..3152e5f9 --- /dev/null +++ b/tests/numbers/floor_check.js @@ -0,0 +1,37 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +/** + * Preample - Harness traits standard library + */ + +traitdef IntChecked; + +function WrapIntCheck(scope, fields) { + fields.forEach(function(field) { + var old = scope[field]; + if (old) { + scope[field] = function() { + return old.apply(this, arguments) as ; + } + } + }); +} + +var opInt = parseInt; +parseInt = function() { opInt.apply(this, arguments) as ; }; + +WrapIntCheck(Math, ['ceil', 'trunc', 'floor', 'round']); + +/** + * End of preamble + */ + + function DoSensetiveOp(data, start: , end: ) { + + } + +if (symbolic A initial true) { + DoSensetiveOp([1,2,3], 5, 6); //Bad: Not necessarily +} else { + DoSensetiveOp([1,2,3], Math.floor(6), parseInt("62", 10)); +} diff --git a/tests/pure/pure_symbol.js b/tests/pure/pure_symbol.js new file mode 100644 index 00000000..f2c65de5 --- /dev/null +++ b/tests/pure/pure_symbol.js @@ -0,0 +1,7 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +var a = symbolic A; + +if (a) { + console.log('A is ' + a) +} diff --git a/tests/regex/anchors.js b/tests/regex/anchors.js new file mode 100644 index 00000000..7c4c1f82 --- /dev/null +++ b/tests/regex/anchors.js @@ -0,0 +1,11 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +'use strict'; + +var q = symbolic q initial ''; + +if (/^--.+=/.test(q)) { + +} + +console.log('Path Finished'); diff --git a/tests/regex/hello_regex.js b/tests/regex/hello_regex.js new file mode 100644 index 00000000..1a7cd40c --- /dev/null +++ b/tests/regex/hello_regex.js @@ -0,0 +1,11 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +if ((symbolic X initial '') == 'HELLO WORLD') {} + +assert 1 == 5; + +if ((symbolic HI initial 'hello').replace(/hello/g, '') === '') { + console.log('Aah'); +} diff --git a/tests/regex/hello_regex2.js b/tests/regex/hello_regex2.js new file mode 100644 index 00000000..ede4fe08 --- /dev/null +++ b/tests/regex/hello_regex2.js @@ -0,0 +1,10 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var a = symbolic HI initial 'hi'; + +if (a.length > 0 && a !== 'hello' && a.replace('h...o', '') === '') { + assert a.length != 0; + console.log('A length: ' + a.length); +} diff --git a/tests/strings/hello_strings.js b/tests/strings/hello_strings.js new file mode 100644 index 00000000..f10eadbf --- /dev/null +++ b/tests/strings/hello_strings.js @@ -0,0 +1,13 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var a = symbolic HELLO initial 'HELLO'; +var b = symbolic NO initial 'NOPE'; + +if (a === b) { + console.log('Yes'); + assert a == b; +} else { + console.log('Nope'); +} diff --git a/tests/strings/hello_strings2.js b/tests/strings/hello_strings2.js new file mode 100644 index 00000000..bf136179 --- /dev/null +++ b/tests/strings/hello_strings2.js @@ -0,0 +1,17 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var a = symbolic A initial 'hello'; + +if (a === "goodbye") { + console.log('PASS'); +} else { + console.log('FAIL'); +} + +if (a === "derp") { + console.log('AND THEN SOME'); +} else { + console.log('NOT THEN SOME'); +} diff --git a/tests/strings/hello_strings_concat.js b/tests/strings/hello_strings_concat.js new file mode 100644 index 00000000..5057a456 --- /dev/null +++ b/tests/strings/hello_strings_concat.js @@ -0,0 +1,12 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var a = symbolic HI initial 'hello'; +var q = a + "DOGS"; + +if (q.length == 6) { + console.log('Hello World'); +} else { + console.log('Goodbye world'); +} diff --git a/tests/strings/hello_strings_concat2.js b/tests/strings/hello_strings_concat2.js new file mode 100644 index 00000000..a33b589c --- /dev/null +++ b/tests/strings/hello_strings_concat2.js @@ -0,0 +1,8 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var a = symbolic DOGS initial 'HELL'; +var q = a + "DOGS666"; + +assert q.length !== 6; diff --git a/tests/strings/hello_strings_len.js b/tests/strings/hello_strings_len.js new file mode 100644 index 00000000..be37a670 --- /dev/null +++ b/tests/strings/hello_strings_len.js @@ -0,0 +1,9 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var q = symbolic HI initial 'HELLO'; + +if (q.length == 15) { + console.log('YES'); +} diff --git a/tests/strings/strings_charat.js b/tests/strings/strings_charat.js new file mode 100644 index 00000000..543e4038 --- /dev/null +++ b/tests/strings/strings_charat.js @@ -0,0 +1,5 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +var x = symbolic A initial 'hello'; + +if (x.substring(0, 2) == 'ab') {} diff --git a/tests/strings/strings_concat.js b/tests/strings/strings_concat.js new file mode 100644 index 00000000..5aba1cc1 --- /dev/null +++ b/tests/strings/strings_concat.js @@ -0,0 +1,9 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +var x = symbolic A initial "Hello"; +var y = symbolic B initial "Goodbye"; + +if (x.concat('abc',y) == "aabcd") { + //One path + console.log('Weird'); +} diff --git a/tests/strings/warning.js b/tests/strings/warning.js new file mode 100644 index 00000000..06ba1325 --- /dev/null +++ b/tests/strings/warning.js @@ -0,0 +1,21 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +"use strict"; + +var x = symbolic X initial 10; +var s = symbolic S initial "foo"; + +console.log("x is initialized to", x); +console.log("s is initialized to", s); + +if (x > 0) { + console.log("x > 0"); +} else { + console.log("x <= 0"); +} + +console.log("prefix " + s + " suffix"); + +if ("x" + s + "z" == "xyz") { + console.log("s is now " + s); +} diff --git a/tests/test_list.js b/tests/test_list.js new file mode 100644 index 00000000..596eade9 --- /dev/null +++ b/tests/test_list.js @@ -0,0 +1,70 @@ +/* Copyright (c) Royal Holloway, University of London | Contact Blake Loring (blake_l@parsed.uk), Duncan Mitchell (Duncan.Mitchell.2015@rhul.ac.uk), or Johannes Kinder (johannes.kinder@rhul.ac.uk) for details or support | LICENSE.md for license details */ + +function buildTestList() { + var testList = []; + + function buildTest(file, expectPaths, expectErrors) { + testList.push({ + path: file, + expectPaths: expectPaths, + expectErrors: expectErrors + }); + } + + //Core Javascript, no symbex / annotations + buildTest('core/bools.js', 1, 0); + buildTest('core/recursion.js', 1, 0); + buildTest('core/lamda.js', 1, 0); + + //Booleans + buildTest('bool/hello.js', 2, 0); + buildTest('bool/basic.js', 4, 1); + + //Pure Symbols + buildTest('pure/pure_symbol.js', 8, 0) + + //Integers + buildTest('integers/hello.js', 2, 0); + buildTest('integers/lt.js', 3, 0); + buildTest('integers/coerce.js', 2, 0); + buildTest('integers/mul.js', 2, 0); + buildTest('integers/breaker.js', 1, 0); + buildTest('integers/infoflow.js', 17, 0); + + //Fractions + buildTest('fractions/fraction_one.js', 3, 0); + + buildTest('assert/assert.js', 6, 0); + + buildTest('assumes/basic.js', 2, 0); + buildTest('assumes/wrapped_assume.js', 2, 0); + + //Loops + buildTest('loops/loop_alot.js', 12, 0); + + //Named methods + buildTest('named_method/simple.js', 1, 0); + + //Strings + buildTest('strings/hello_strings.js', 2, 0); + buildTest('strings/hello_strings2.js', 3, 0); + buildTest('strings/hello_strings_concat.js', 2, 0); + buildTest('strings/hello_strings_concat2.js', 1, 0); + buildTest('strings/hello_strings_len.js', 2, 0); + buildTest('strings/strings_concat.js', 2, 0); + buildTest('strings/warning.js', 4, 0); + + /* + //Regex + //buildTest('regex/hello_regex.js', 2, 0); + //buildTest('regex/hello_regex2.js', 4, 0); + */ + + //Async + buildTest('async/settimeout.js', 3, 1); + + return testList; +} + +exports["default"] = buildTestList(); +module.exports = exports["default"];