├── .gitignore ├── OPCODES.md ├── TODO.txt ├── USEFUL.md ├── package-lock.json ├── package.json └── src ├── compiler ├── debug_output │ └── test.js ├── handlers │ ├── callExpression.js │ ├── example.js │ ├── expressionStatement.js │ ├── index.js │ ├── variableDeclaration.js │ └── variableDeclarator.js ├── index.js └── samples │ ├── 1.js │ ├── 2.js │ ├── 3.js │ ├── 4.js │ └── 5.js ├── constants └── index.js ├── testASM.js ├── testCompiler.js └── vm ├── index.js └── parser.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /OPCODES.md: -------------------------------------------------------------------------------- 1 | **Simple arithmitic** 2 | 3 | - `ADD`: Pops 2 elements, adds them together and pushes the result onto the stack 4 | - `SUB` Pops 2 elements, subtracts them and pushes the result onto the stack 5 | - `MUL` Pops 2 elements, multiplies them and pushes the result onto the stack 6 | - `DIV` Pops 2 elements, divides them and pushes the result onto the stack 7 | - `MOD` Pops 2 elements, uses the modulo operator on them and pushes the result onto the stack 8 | - `NEG` Pops 1 element, uses the ! operator on it and pushes the result onto the stack 9 | 10 | **Logical** 11 | 12 | - `EQUAL` Pops 2 elements, checks if they're equal and pushes the result onto the stack 13 | - `NOT_EQUAL` Pops 2 elements, checks if they're not equal and pushes the result onto the stack 14 | - `STRICT_EQUAL` Pops 2 elements, checks if they're strictly equal and pushes the result onto the stack 15 | - `STRICT_NOT_EQUAL` Pops 2 elements, checks if they're strictly not equal and pushes the result onto the stack 16 | - `GREATER_THAN` Pops 2 elements, checks if the top one is greater and pushes the result onto the stack 17 | - `LESS_THAN` Pops 2 elements, checks if the top one is smaller and pushes the result onto the stack 18 | - `LESS_THAN_EQUAL` Pops 2 elements, checks if the top one is smaller or the same and pushes the result onto the stack 19 | - `GREATER_THAN_EQUAL` Pops 2 elements, checks if the top one is greater or the same and pushes the result onto the stack 20 | 21 | **Bitwise** 22 | 23 | - `AND` Pops 2 elements, uses the `&` operator and pushes the result onto the stack 24 | - `OR` Pops 2 elements, uses the `|` operator and pushes the result onto the stack 25 | - `XOR` Pops 2 elements, uses the `^` operator and pushes the result onto the stack 26 | - `NOT` Pops 2 elements, uses the `~` operator and pushes the result onto the stack 27 | - `LEFT_SHIFT` Pops 2 elements, uses the `<<` operator and pushes the result onto the stack 28 | - `RIGHT_SHIFT` Pops 2 elements, uses the `>>` operator and pushes the result onto the stack 29 | - `ZERO_LEFT_SHIFT` Pops 2 elements, uses the `>>>` operator and pushes the result onto the stack 30 | 31 | **Stack operations** 32 | 33 | - `PUSH` Pushes the next byte onto the stack 34 | - `JMP` Jumps the pointer to the next byte 35 | - `JMP_IF` Jumps the pointer to the next byte if the top argument on the stack is also true (and pops that one) 36 | 37 | **Object operations** 38 | 39 | - `PUSH_THIS` Pushes the `window` (or global) keyword onto the stack 40 | - `MEMBER_EXPRESSION` Gets the last 2 from the stack, pushes the result of `a[b]` to the stack 41 | - `PUSH_ARRAY` Creates an empty array and pushes it to the stack 42 | - `PUSH_OBJECT` Creates an empty object and pushes it to the stack 43 | - `PUSH_TO_ARRAY` Gets the top element from the stack, and pushes it to the second element on the stack 44 | 45 | **Other (for now)** 46 | 47 | - `EXECUTE_FUNCTION` Executes the function at the top of the stack, passes in the destructured arguments from the array in the second top in the stack, if the type of the var is Array. If not, just use that as the only argument 48 | 49 | **VM operations** 50 | 51 | - `HLT` Halts the execution 52 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | [ ] create the actual VM 2 | - [X] create parser for text -> opcodes 3 | - [ ] create the system for strings (probably just array selection) 4 | - [ ] create all the different op codes 5 | - [X] create the vm handler object, contains stack, pointer, etc. 6 | - [X] create actual VM execution function (shape does it nicely, can referr to that) 7 | 8 | [ ] create the compiler 9 | WIP, for now we can focus on the basics 10 | - [X] handle VariableDeclaration 11 | - [X] handle CallExpression (not wrapped FunctionExpression for now) 12 | - [X] handle CallExpression inputs and outputs 13 | - [X] make call expression inputs make use of an array so if can have infinite inputs or 0 inputs, currently it only supports 1 input and it has to be a string (i think). 14 | - [X] support numeric literals as callexpress inputs when not proxied thru a variable VariableDeclaration 15 | - [ ] handle arrays 16 | - [ ] for loops 17 | - [ ] objects 18 | - [ ] custom functions (would be a set of instructions and pointer map pushed into the stack and used in .run()?) 19 | - [ ] if else statements 20 | - [ ] all different types of math 21 | 22 | Code optimizations: 23 | - [X] Make the compiler a class 24 | - [ ] Make a function for adding bytes 25 | - [ ] Make a function for adding pointers -------------------------------------------------------------------------------- /USEFUL.md: -------------------------------------------------------------------------------- 1 | # Useful links 2 | 3 | ## Github 4 | 5 | - [Chinese JS VM](https://github.com/bramblex/jsjs-vm-demo) 6 | - [Shape decompiler toolkit](https://github.com/g2asell2019/shape-security-decompiler-toolkit/tree/master/deobfuscator) 7 | - [Kasada decompiler + frontend (opcodes.fr)](https://github.com/OPCODES-GITHUB/dasaka-UI) 8 | - [Kechinator JSVM](https://github.com/Kechinator/jsvm) 9 | 10 | ## Videos 11 | 12 | - [16 bit VM in JavaScript (YT series, register based)](https://www.youtube.com/watch?v=ftbwd3sb5mw&list=plp29wdx6qmw5ddwpdwhcrjseubs5nrq9b) 13 | 14 | ## Blogs 15 | 16 | - [Kasada VM reversal (opcodes.fr)](https://opcodes.fr/publications/2021-08/kasada-javascript-vm-obfuscation-reverse-part1) 17 | - [Devirt nike VM (kasada), nullpt.rs](https://www.nullpt.rs/devirtualizing-nike-vm-2) 18 | - [Building a JS VM](https://jwillbold.com/posts/obfuscation/2019-06-16-the-secret-guide-to-virtualization-obfuscation-in-javascript/) 19 | - [Stack based VM (rust)](https://dev.to/jimsy/building-a-stack-based-virtual-machine-part-6---function-calls-2md5) 20 | 21 | ## Other 22 | 23 | - [Reddit comment of botguard dev](https://www.reddit.com/r/programming/comments/10755l2/comment/j3lwqbc/?utm_source=share&utm_medium=web2x&context=3) 24 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-jsvm", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "custom-jsvm", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@babel/generator": "^7.21.3", 13 | "@babel/parser": "^7.21.3", 14 | "@babel/traverse": "^7.21.3", 15 | "@babel/types": "^7.21.3" 16 | } 17 | }, 18 | "node_modules/@babel/code-frame": { 19 | "version": "7.18.6", 20 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", 21 | "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", 22 | "dependencies": { 23 | "@babel/highlight": "^7.18.6" 24 | }, 25 | "engines": { 26 | "node": ">=6.9.0" 27 | } 28 | }, 29 | "node_modules/@babel/generator": { 30 | "version": "7.21.3", 31 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", 32 | "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", 33 | "dependencies": { 34 | "@babel/types": "^7.21.3", 35 | "@jridgewell/gen-mapping": "^0.3.2", 36 | "@jridgewell/trace-mapping": "^0.3.17", 37 | "jsesc": "^2.5.1" 38 | }, 39 | "engines": { 40 | "node": ">=6.9.0" 41 | } 42 | }, 43 | "node_modules/@babel/helper-environment-visitor": { 44 | "version": "7.18.9", 45 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", 46 | "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", 47 | "engines": { 48 | "node": ">=6.9.0" 49 | } 50 | }, 51 | "node_modules/@babel/helper-function-name": { 52 | "version": "7.21.0", 53 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", 54 | "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", 55 | "dependencies": { 56 | "@babel/template": "^7.20.7", 57 | "@babel/types": "^7.21.0" 58 | }, 59 | "engines": { 60 | "node": ">=6.9.0" 61 | } 62 | }, 63 | "node_modules/@babel/helper-hoist-variables": { 64 | "version": "7.18.6", 65 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", 66 | "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", 67 | "dependencies": { 68 | "@babel/types": "^7.18.6" 69 | }, 70 | "engines": { 71 | "node": ">=6.9.0" 72 | } 73 | }, 74 | "node_modules/@babel/helper-split-export-declaration": { 75 | "version": "7.18.6", 76 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", 77 | "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", 78 | "dependencies": { 79 | "@babel/types": "^7.18.6" 80 | }, 81 | "engines": { 82 | "node": ">=6.9.0" 83 | } 84 | }, 85 | "node_modules/@babel/helper-string-parser": { 86 | "version": "7.19.4", 87 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", 88 | "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", 89 | "engines": { 90 | "node": ">=6.9.0" 91 | } 92 | }, 93 | "node_modules/@babel/helper-validator-identifier": { 94 | "version": "7.19.1", 95 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", 96 | "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", 97 | "engines": { 98 | "node": ">=6.9.0" 99 | } 100 | }, 101 | "node_modules/@babel/highlight": { 102 | "version": "7.18.6", 103 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", 104 | "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", 105 | "dependencies": { 106 | "@babel/helper-validator-identifier": "^7.18.6", 107 | "chalk": "^2.0.0", 108 | "js-tokens": "^4.0.0" 109 | }, 110 | "engines": { 111 | "node": ">=6.9.0" 112 | } 113 | }, 114 | "node_modules/@babel/parser": { 115 | "version": "7.21.3", 116 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", 117 | "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", 118 | "bin": { 119 | "parser": "bin/babel-parser.js" 120 | }, 121 | "engines": { 122 | "node": ">=6.0.0" 123 | } 124 | }, 125 | "node_modules/@babel/template": { 126 | "version": "7.20.7", 127 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", 128 | "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", 129 | "dependencies": { 130 | "@babel/code-frame": "^7.18.6", 131 | "@babel/parser": "^7.20.7", 132 | "@babel/types": "^7.20.7" 133 | }, 134 | "engines": { 135 | "node": ">=6.9.0" 136 | } 137 | }, 138 | "node_modules/@babel/traverse": { 139 | "version": "7.21.3", 140 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", 141 | "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", 142 | "dependencies": { 143 | "@babel/code-frame": "^7.18.6", 144 | "@babel/generator": "^7.21.3", 145 | "@babel/helper-environment-visitor": "^7.18.9", 146 | "@babel/helper-function-name": "^7.21.0", 147 | "@babel/helper-hoist-variables": "^7.18.6", 148 | "@babel/helper-split-export-declaration": "^7.18.6", 149 | "@babel/parser": "^7.21.3", 150 | "@babel/types": "^7.21.3", 151 | "debug": "^4.1.0", 152 | "globals": "^11.1.0" 153 | }, 154 | "engines": { 155 | "node": ">=6.9.0" 156 | } 157 | }, 158 | "node_modules/@babel/types": { 159 | "version": "7.21.3", 160 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", 161 | "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", 162 | "dependencies": { 163 | "@babel/helper-string-parser": "^7.19.4", 164 | "@babel/helper-validator-identifier": "^7.19.1", 165 | "to-fast-properties": "^2.0.0" 166 | }, 167 | "engines": { 168 | "node": ">=6.9.0" 169 | } 170 | }, 171 | "node_modules/@jridgewell/gen-mapping": { 172 | "version": "0.3.2", 173 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", 174 | "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", 175 | "dependencies": { 176 | "@jridgewell/set-array": "^1.0.1", 177 | "@jridgewell/sourcemap-codec": "^1.4.10", 178 | "@jridgewell/trace-mapping": "^0.3.9" 179 | }, 180 | "engines": { 181 | "node": ">=6.0.0" 182 | } 183 | }, 184 | "node_modules/@jridgewell/resolve-uri": { 185 | "version": "3.1.0", 186 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 187 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 188 | "engines": { 189 | "node": ">=6.0.0" 190 | } 191 | }, 192 | "node_modules/@jridgewell/set-array": { 193 | "version": "1.1.2", 194 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 195 | "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", 196 | "engines": { 197 | "node": ">=6.0.0" 198 | } 199 | }, 200 | "node_modules/@jridgewell/sourcemap-codec": { 201 | "version": "1.4.14", 202 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 203 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" 204 | }, 205 | "node_modules/@jridgewell/trace-mapping": { 206 | "version": "0.3.17", 207 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", 208 | "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", 209 | "dependencies": { 210 | "@jridgewell/resolve-uri": "3.1.0", 211 | "@jridgewell/sourcemap-codec": "1.4.14" 212 | } 213 | }, 214 | "node_modules/ansi-styles": { 215 | "version": "3.2.1", 216 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 217 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 218 | "dependencies": { 219 | "color-convert": "^1.9.0" 220 | }, 221 | "engines": { 222 | "node": ">=4" 223 | } 224 | }, 225 | "node_modules/chalk": { 226 | "version": "2.4.2", 227 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 228 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 229 | "dependencies": { 230 | "ansi-styles": "^3.2.1", 231 | "escape-string-regexp": "^1.0.5", 232 | "supports-color": "^5.3.0" 233 | }, 234 | "engines": { 235 | "node": ">=4" 236 | } 237 | }, 238 | "node_modules/color-convert": { 239 | "version": "1.9.3", 240 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 241 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 242 | "dependencies": { 243 | "color-name": "1.1.3" 244 | } 245 | }, 246 | "node_modules/color-name": { 247 | "version": "1.1.3", 248 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 249 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 250 | }, 251 | "node_modules/debug": { 252 | "version": "4.3.4", 253 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 254 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 255 | "dependencies": { 256 | "ms": "2.1.2" 257 | }, 258 | "engines": { 259 | "node": ">=6.0" 260 | }, 261 | "peerDependenciesMeta": { 262 | "supports-color": { 263 | "optional": true 264 | } 265 | } 266 | }, 267 | "node_modules/escape-string-regexp": { 268 | "version": "1.0.5", 269 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 270 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 271 | "engines": { 272 | "node": ">=0.8.0" 273 | } 274 | }, 275 | "node_modules/globals": { 276 | "version": "11.12.0", 277 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 278 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 279 | "engines": { 280 | "node": ">=4" 281 | } 282 | }, 283 | "node_modules/has-flag": { 284 | "version": "3.0.0", 285 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 286 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 287 | "engines": { 288 | "node": ">=4" 289 | } 290 | }, 291 | "node_modules/js-tokens": { 292 | "version": "4.0.0", 293 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 294 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 295 | }, 296 | "node_modules/jsesc": { 297 | "version": "2.5.2", 298 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 299 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", 300 | "bin": { 301 | "jsesc": "bin/jsesc" 302 | }, 303 | "engines": { 304 | "node": ">=4" 305 | } 306 | }, 307 | "node_modules/ms": { 308 | "version": "2.1.2", 309 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 310 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 311 | }, 312 | "node_modules/supports-color": { 313 | "version": "5.5.0", 314 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 315 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 316 | "dependencies": { 317 | "has-flag": "^3.0.0" 318 | }, 319 | "engines": { 320 | "node": ">=4" 321 | } 322 | }, 323 | "node_modules/to-fast-properties": { 324 | "version": "2.0.0", 325 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 326 | "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", 327 | "engines": { 328 | "node": ">=4" 329 | } 330 | } 331 | }, 332 | "dependencies": { 333 | "@babel/code-frame": { 334 | "version": "7.18.6", 335 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", 336 | "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", 337 | "requires": { 338 | "@babel/highlight": "^7.18.6" 339 | } 340 | }, 341 | "@babel/generator": { 342 | "version": "7.21.3", 343 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", 344 | "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", 345 | "requires": { 346 | "@babel/types": "^7.21.3", 347 | "@jridgewell/gen-mapping": "^0.3.2", 348 | "@jridgewell/trace-mapping": "^0.3.17", 349 | "jsesc": "^2.5.1" 350 | } 351 | }, 352 | "@babel/helper-environment-visitor": { 353 | "version": "7.18.9", 354 | "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", 355 | "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" 356 | }, 357 | "@babel/helper-function-name": { 358 | "version": "7.21.0", 359 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", 360 | "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", 361 | "requires": { 362 | "@babel/template": "^7.20.7", 363 | "@babel/types": "^7.21.0" 364 | } 365 | }, 366 | "@babel/helper-hoist-variables": { 367 | "version": "7.18.6", 368 | "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", 369 | "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", 370 | "requires": { 371 | "@babel/types": "^7.18.6" 372 | } 373 | }, 374 | "@babel/helper-split-export-declaration": { 375 | "version": "7.18.6", 376 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", 377 | "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", 378 | "requires": { 379 | "@babel/types": "^7.18.6" 380 | } 381 | }, 382 | "@babel/helper-string-parser": { 383 | "version": "7.19.4", 384 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", 385 | "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" 386 | }, 387 | "@babel/helper-validator-identifier": { 388 | "version": "7.19.1", 389 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", 390 | "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" 391 | }, 392 | "@babel/highlight": { 393 | "version": "7.18.6", 394 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", 395 | "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", 396 | "requires": { 397 | "@babel/helper-validator-identifier": "^7.18.6", 398 | "chalk": "^2.0.0", 399 | "js-tokens": "^4.0.0" 400 | } 401 | }, 402 | "@babel/parser": { 403 | "version": "7.21.3", 404 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", 405 | "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==" 406 | }, 407 | "@babel/template": { 408 | "version": "7.20.7", 409 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", 410 | "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", 411 | "requires": { 412 | "@babel/code-frame": "^7.18.6", 413 | "@babel/parser": "^7.20.7", 414 | "@babel/types": "^7.20.7" 415 | } 416 | }, 417 | "@babel/traverse": { 418 | "version": "7.21.3", 419 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", 420 | "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", 421 | "requires": { 422 | "@babel/code-frame": "^7.18.6", 423 | "@babel/generator": "^7.21.3", 424 | "@babel/helper-environment-visitor": "^7.18.9", 425 | "@babel/helper-function-name": "^7.21.0", 426 | "@babel/helper-hoist-variables": "^7.18.6", 427 | "@babel/helper-split-export-declaration": "^7.18.6", 428 | "@babel/parser": "^7.21.3", 429 | "@babel/types": "^7.21.3", 430 | "debug": "^4.1.0", 431 | "globals": "^11.1.0" 432 | } 433 | }, 434 | "@babel/types": { 435 | "version": "7.21.3", 436 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", 437 | "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", 438 | "requires": { 439 | "@babel/helper-string-parser": "^7.19.4", 440 | "@babel/helper-validator-identifier": "^7.19.1", 441 | "to-fast-properties": "^2.0.0" 442 | } 443 | }, 444 | "@jridgewell/gen-mapping": { 445 | "version": "0.3.2", 446 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", 447 | "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", 448 | "requires": { 449 | "@jridgewell/set-array": "^1.0.1", 450 | "@jridgewell/sourcemap-codec": "^1.4.10", 451 | "@jridgewell/trace-mapping": "^0.3.9" 452 | } 453 | }, 454 | "@jridgewell/resolve-uri": { 455 | "version": "3.1.0", 456 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 457 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" 458 | }, 459 | "@jridgewell/set-array": { 460 | "version": "1.1.2", 461 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 462 | "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" 463 | }, 464 | "@jridgewell/sourcemap-codec": { 465 | "version": "1.4.14", 466 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 467 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" 468 | }, 469 | "@jridgewell/trace-mapping": { 470 | "version": "0.3.17", 471 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", 472 | "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", 473 | "requires": { 474 | "@jridgewell/resolve-uri": "3.1.0", 475 | "@jridgewell/sourcemap-codec": "1.4.14" 476 | } 477 | }, 478 | "ansi-styles": { 479 | "version": "3.2.1", 480 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 481 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 482 | "requires": { 483 | "color-convert": "^1.9.0" 484 | } 485 | }, 486 | "chalk": { 487 | "version": "2.4.2", 488 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 489 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 490 | "requires": { 491 | "ansi-styles": "^3.2.1", 492 | "escape-string-regexp": "^1.0.5", 493 | "supports-color": "^5.3.0" 494 | } 495 | }, 496 | "color-convert": { 497 | "version": "1.9.3", 498 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 499 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 500 | "requires": { 501 | "color-name": "1.1.3" 502 | } 503 | }, 504 | "color-name": { 505 | "version": "1.1.3", 506 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 507 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 508 | }, 509 | "debug": { 510 | "version": "4.3.4", 511 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 512 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 513 | "requires": { 514 | "ms": "2.1.2" 515 | } 516 | }, 517 | "escape-string-regexp": { 518 | "version": "1.0.5", 519 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 520 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" 521 | }, 522 | "globals": { 523 | "version": "11.12.0", 524 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 525 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" 526 | }, 527 | "has-flag": { 528 | "version": "3.0.0", 529 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 530 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" 531 | }, 532 | "js-tokens": { 533 | "version": "4.0.0", 534 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 535 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 536 | }, 537 | "jsesc": { 538 | "version": "2.5.2", 539 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", 540 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" 541 | }, 542 | "ms": { 543 | "version": "2.1.2", 544 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 545 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 546 | }, 547 | "supports-color": { 548 | "version": "5.5.0", 549 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 550 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 551 | "requires": { 552 | "has-flag": "^3.0.0" 553 | } 554 | }, 555 | "to-fast-properties": { 556 | "version": "2.0.0", 557 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 558 | "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" 559 | } 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-jsvm", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "run": "node ./src" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/post04/custom-JSVM.git" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/post04/custom-JSVM/issues" 19 | }, 20 | "homepage": "https://github.com/post04/custom-JSVM#readme", 21 | "dependencies": { 22 | "@babel/generator": "^7.21.3", 23 | "@babel/parser": "^7.21.3", 24 | "@babel/traverse": "^7.21.3", 25 | "@babel/types": "^7.21.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/compiler/debug_output/test.js: -------------------------------------------------------------------------------- 1 | // varaible declaration 2 | const test = 'test'; 3 | // call expression with input 4 | console.log("BYTECODE_0"); 5 | btoa("BYTECODE_0"); -------------------------------------------------------------------------------- /src/compiler/handlers/callExpression.js: -------------------------------------------------------------------------------- 1 | const t = require('@babel/types'); 2 | 3 | // ! opcodes 4 | const { 5 | Opcodes 6 | } = require('../../constants'); 7 | // ! opcodes 8 | 9 | 10 | module.exports = 11 | (path, node, index, handler) => { 12 | var bytes = []; 13 | var order = []; 14 | 15 | if (node.callee.type == 'MemberExpression') { 16 | // TODO: support recursiveness? Could be really annoying cause of all the edge cases. 17 | if (node.callee.property.type != 'Identifier') { 18 | console.log('unsupported callee property:', node.callee.property.type); 19 | exit(999); 20 | return; 21 | } 22 | // TODO: check if it's a defined function or not, if it is, we need to point to push the vm object? 23 | // ! push the THIS object 24 | bytes.push(...[Opcodes['PUSH_THIS']]); 25 | order.push(index++); 26 | // ! push the object 27 | bytes.push(...[Opcodes['PUSH'], node.callee.object.name]); 28 | order.push(index++); 29 | order.push(index++); 30 | // ! push the MEMBER_EXPRESSION op code 31 | bytes.push(...[Opcodes['MEMBER_EXPRESSION']]); 32 | order.push(index++); 33 | // ! push the identifier 34 | bytes.push(...[Opcodes['PUSH'], node.callee.property.name]); 35 | order.push(index++); 36 | order.push(index++); 37 | // ! recap: this, object, member_expression, property 38 | } 39 | 40 | if (node.callee.type == 'Identifier') { 41 | // TODO: check if it's a defined function or not, if it is, we need to point to push the vm object? 42 | // ! push the THIS object 43 | bytes.push(...[Opcodes['PUSH_THIS']]); 44 | order.push(index++); 45 | // ! push the function identifier name 46 | bytes.push(...[Opcodes['PUSH'], node.callee.name]); 47 | order.push(index++); 48 | order.push(index++); 49 | } 50 | 51 | // ! push the MEMBER_EXPRESSION op code 52 | bytes.push(...[Opcodes['MEMBER_EXPRESSION']]); 53 | order.push(index++); 54 | 55 | // ! at this point, we assume the argument(s) supplied in the function are already in the byte code 56 | 57 | // ! we need to return the instruction to execute for the param 58 | // ! Push empty array (for args) 59 | bytes.push(Opcodes.PUSH_ARRAY); 60 | order.push(index++); 61 | node.arguments.forEach((arg) => { 62 | console.log('[CallEx arg]', arg.value); 63 | 64 | const isBytecodeExpression = 65 | arg.type === 'StringLiteral' && arg.value.startsWith('BYTECODE_'); 66 | 67 | if (isBytecodeExpression) { 68 | const bytecodePos = parseInt(arg.value.split('_')[1]); 69 | order.push(bytecodePos); 70 | order.push(order[order.length - 1] + 1); 71 | } else if (arg.value) { 72 | bytes.push(Opcodes.PUSH); 73 | order.push(index++); 74 | bytes.push(arg.value); 75 | order.push(index++); 76 | } else { 77 | throw new Error(`Unhandled function argument! ${arg.type}`) 78 | } 79 | 80 | bytes.push(Opcodes.PUSH_TO_ARRAY); 81 | order.push(index++); 82 | }); 83 | 84 | // ! now we tell it to run the function 85 | bytes.push(...[Opcodes['EXECUTE_FUNCTION']]); 86 | order.push(index++); 87 | return [order, bytes]; 88 | }; -------------------------------------------------------------------------------- /src/compiler/handlers/example.js: -------------------------------------------------------------------------------- 1 | const t = require('@babel/types'); 2 | const { 3 | Opcodes 4 | } = require('../../constants'); 5 | 6 | /* 7 | EXAMPLE FILE - DON'T MODIFY 8 | */ 9 | 10 | module.exports = (path, node, index, handler) => { 11 | let bytes = [] 12 | let order = [] 13 | 14 | // do stuff 15 | 16 | return [order, bytes] 17 | } -------------------------------------------------------------------------------- /src/compiler/handlers/expressionStatement.js: -------------------------------------------------------------------------------- 1 | module.exports = (path, node, index, handler) => { 2 | return handler(path, node.expression, index, handler) 3 | } -------------------------------------------------------------------------------- /src/compiler/handlers/index.js: -------------------------------------------------------------------------------- 1 | const handlers = { 2 | ExpressionStatement: require("./expressionStatement"), 3 | CallExpression: require('./callExpression'), 4 | VariableDeclarator: require('./variableDeclarator'), 5 | VariableDeclaration: require("./variableDeclaration"), 6 | }; 7 | 8 | const handleNode = (path, node, i) => { 9 | const handler = handlers[node.type]; 10 | if (!handler) throw new Error(`Unsupported AST node: ${node.type}`); 11 | console.log(`Calling ${node.type} handler`, typeof handler) 12 | return handler(path, node, i, handleNode); 13 | } 14 | 15 | module.exports = handleNode -------------------------------------------------------------------------------- /src/compiler/handlers/variableDeclaration.js: -------------------------------------------------------------------------------- 1 | module.exports = (path, node, index, handler) => { 2 | let bytes = [] 3 | let order = [] 4 | 5 | node.declarations.forEach(declarationNode => { 6 | const res = handler(path, declarationNode, index, handler) 7 | order.push(...res[0]) 8 | bytes.push(...res[1]) 9 | }) 10 | 11 | return [order, bytes] 12 | } -------------------------------------------------------------------------------- /src/compiler/handlers/variableDeclarator.js: -------------------------------------------------------------------------------- 1 | const t = require('@babel/types'); 2 | 3 | // ! opcodes 4 | const { 5 | Opcodes 6 | } = require('../../constants'); 7 | // ! opcodes 8 | 9 | const callExpression = require('./callExpression'); 10 | 11 | module.exports = 12 | // path = VariableDeclaration path 13 | // node = VariableDeclarator node 14 | // index = current index in the byte code 15 | // returns the byte code for this operation and modifys existing uses of this variable 16 | (path, node, index, handler) => { 17 | let bytes = []; 18 | let order = []; 19 | 20 | // ! first we make sure it's something we can handle correctly 21 | // TODO: add support for everything else (including redirected definitions to other op codes) 22 | if (node.init?.type === 'CallExpression') { 23 | const [outOrder, outBytes] = callExpression( 24 | path.get('init'), 25 | node.init, 26 | index 27 | ); 28 | bytes.push(...outBytes); 29 | order.push(...outOrder); 30 | console.log(node); 31 | console.log('unsupported init type:', node.init.type); 32 | exit(999); 33 | } else { 34 | bytes.push(Opcodes.PUSH); 35 | bytes.push(node.init?.value); 36 | } 37 | 38 | // ! now we need to replace all uses of this variable with an indicator that it's now on the stack at x position 39 | const binding = path.scope.getBinding(node.id.name); 40 | if (!binding) { 41 | console.log('No binding to ', JSON.stringify(node)); 42 | exit(999); 43 | } 44 | const { 45 | referencePaths 46 | } = binding; 47 | for (let refPath of referencePaths) { 48 | refPath.replaceWith(t.stringLiteral('BYTECODE_' + index)); 49 | } 50 | // ! return byte code :) 51 | return [order, bytes]; 52 | }; -------------------------------------------------------------------------------- /src/compiler/index.js: -------------------------------------------------------------------------------- 1 | const { 2 | readFileSync, 3 | writeFileSync 4 | } = require('node:fs'); 5 | // ! babel 6 | const t = require('@babel/types'); 7 | const parser = require('@babel/parser'); 8 | const traverse = require('@babel/traverse').default; 9 | const generate = require('@babel/generator').default; 10 | 11 | const handleNode = require("./handlers") 12 | 13 | const log = (...a) => { 14 | // if (!this.debug) return; 15 | console.log('[DEBUG | COMP]', ...a); 16 | }; 17 | 18 | // ! opcodes 19 | const { 20 | Opcodes 21 | } = require('../constants'); 22 | // ! opcodes 23 | 24 | 25 | const handleProgram = (ast) => { 26 | let order = []; 27 | let bytecode = []; 28 | traverse(ast, { 29 | Program(path) { 30 | let { 31 | node 32 | } = path; 33 | node.body.forEach((node) => { 34 | log(`Compiling ${node.type}...`) 35 | const [newOrder, newBytes] = handleNode(path, node, bytecode.length) 36 | log(`Done! Order: ${order}, Bytes: ${newBytes}`) 37 | order.push(...newOrder); 38 | bytecode.push(...newBytes); 39 | }); 40 | }, 41 | }); 42 | console.log("order:", order, "bytes:", bytecode) 43 | return { 44 | order, 45 | bytecode 46 | }; 47 | }; 48 | 49 | const handleStringLiterals = (ast, bytecode) => { 50 | traverse(ast, { 51 | StringLiteral(path) { 52 | if (path.parentPath.node.type != 'VariableDeclarator') { 53 | var cache = path.node.value; 54 | path.node.value = 'BYTECODE_' + bytecode.length; 55 | // path.replaceWith(t.stringLiteral("BYTECODE_" + bytecode.length)) 56 | bytecode.push(...[Opcodes['PUSH'], cache]); 57 | } 58 | }, 59 | }); 60 | return bytecode; 61 | }; 62 | 63 | module.exports = class Compiler { 64 | constructor() { 65 | this.log = log; 66 | } 67 | 68 | compileFromString(src) { 69 | const AST = parser.parse(src, {}); 70 | let { 71 | bytecode, 72 | order 73 | } = handleProgram(AST); 74 | bytecode = handleStringLiterals(AST, bytecode); 75 | this.bytecode = bytecode; 76 | this.order = order; 77 | return { 78 | bytecode, 79 | order 80 | }; 81 | } 82 | compileFromFile(srcPath) { 83 | const src = readFileSync(srcPath, 'utf-8'); 84 | return this.compileFromString(src); 85 | } 86 | compileFromBase64(src) { 87 | const str = Buffer.from(src, 'base64').toString('utf-8'); 88 | const p = JSON.parse(str); 89 | return { 90 | bytecode: p[0], 91 | order: p[1] 92 | }; 93 | } 94 | base64() { 95 | if (!this.bytecode || !this.order) 96 | throw new Error('Compile some code first!'); 97 | 98 | const str = JSON.stringify([this.bytecode, this.order]); 99 | return Buffer.from(str, 'utf-8').toString('base64'); 100 | } 101 | writeDebugFile(output = './debug_output/test.js') { 102 | writeFileSync( 103 | output, 104 | generate(AST, { 105 | comments: true, 106 | minified: false, 107 | concise: false, 108 | }).code 109 | ); 110 | } 111 | }; -------------------------------------------------------------------------------- /src/compiler/samples/1.js: -------------------------------------------------------------------------------- 1 | // call expression with no input 2 | console.log() -------------------------------------------------------------------------------- /src/compiler/samples/2.js: -------------------------------------------------------------------------------- 1 | // call expression with input 2 | console.log("test") -------------------------------------------------------------------------------- /src/compiler/samples/3.js: -------------------------------------------------------------------------------- 1 | // varaible declaration 2 | const test = 'test'; 3 | // call expression with input 4 | console.log(test); 5 | -------------------------------------------------------------------------------- /src/compiler/samples/4.js: -------------------------------------------------------------------------------- 1 | // ! do not worry about compiling the function for now 2 | function log(text) { 3 | console.log(text) 4 | } 5 | 6 | // single call expression, no member expression here so may be easier for beginning 7 | log() 8 | log("test") -------------------------------------------------------------------------------- /src/compiler/samples/5.js: -------------------------------------------------------------------------------- 1 | // ! don't worry about compiling this function for now 2 | function add(a, b) { 3 | return a + b 4 | } 5 | 6 | // ! do not worry about compiling the function for now 7 | function log(text) { 8 | console.log(text) 9 | } 10 | 11 | // call expression with input and output 12 | log(add(1, 2)) 13 | // now try with variable declaration 14 | const res = add(1, 2) 15 | log(res) -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | let c = 0; 2 | 3 | const opcodes = { 4 | // Simple arithmitic 5 | ADD: c++, // + 6 | SUB: c++, // - 7 | MUL: c++, // * 8 | DIV: c++, // / 9 | MOD: c++, // % 10 | NEG: c++, // ! 11 | 12 | // Logical 13 | EQUAL: c++, // == 14 | NOT_EQUAL: c++, // != 15 | STRICT_EQUAL: c++, // === 16 | STRICT_NOT_EQUAL: c++, // !== 17 | GREATER_THAN: c++, // > 18 | LESS_THAN: c++, // < 19 | GREATER_THAN_EQUAL: c++, // >= 20 | LESS_THAN_EQUAL: c++, // <= 21 | 22 | // Bitwise 23 | AND: c++, // & 24 | OR: c++, // | 25 | XOR: c++, // ^ 26 | NOT: c++, // ~ 27 | LEFT_SHIFT: c++, // << 28 | RIGHT_SHIFT: c++, // >> 29 | ZERO_LEFT_SHIFT: c++, // >>> 30 | 31 | // Stack operations 32 | PUSH: c++, 33 | JMP: c++, 34 | JMP_IF: c++, 35 | 36 | // Object operations 37 | PUSH_THIS: c++, 38 | MEMBER_EXPRESSION: c++, 39 | PUSH_ARRAY: c++, 40 | PUSH_OBJECT: c++, 41 | PUSH_TO_ARRAY: c++, 42 | 43 | // Other (for now) 44 | EXECUTE_FUNCTION: c++, 45 | 46 | // VM operations 47 | HLT: c++, 48 | }; 49 | 50 | module.exports = { 51 | Opcodes: opcodes, 52 | numToOp(num) { 53 | res = Object.entries(opcodes).find((el) => el[1] == num); 54 | return res?.[0] || num; 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /src/testASM.js: -------------------------------------------------------------------------------- 1 | const VirtualMachine = require('./vm'); 2 | const parser = require('./vm/parser'); 3 | 4 | const { program, labelMap } = parser(` 5 | PUSH_ARRAY 6 | PUSH 0x10 7 | PUSH_TO_ARRAY 8 | PUSH 0x15 9 | PUSH_TO_ARRAY 10 | `); 11 | console.log(program, labelMap); 12 | const vm = new VirtualMachine(program, labelMap, [0, 1, 2, 3, 4, 5, 6]); 13 | vm.debug = true; 14 | vm.run(); 15 | console.log('Stack:', vm.stack); 16 | -------------------------------------------------------------------------------- /src/testCompiler.js: -------------------------------------------------------------------------------- 1 | const Compiler = require('./compiler'); 2 | const VirtualMachine = require('./vm'); 3 | 4 | const code = ` 5 | let a = "Hello" 6 | console.log(a, 'PianoMan'); 7 | 1+2 8 | `; 9 | 10 | const compiler = new Compiler(); 11 | 12 | // const { bytecode, order } = new Compiler().compileFromFile( 13 | // './compiler/samples/3.js' 14 | // ); 15 | const { 16 | bytecode, 17 | order 18 | } = compiler.compileFromString(code); 19 | // console.log(compiler.base64()); 20 | // console.log(compiler.compileFromBase64(compiler.base64())); 21 | console.log(bytecode, order); 22 | const vm = new VirtualMachine(bytecode, [], order); 23 | vm.debug = true; 24 | vm.run(); -------------------------------------------------------------------------------- /src/vm/index.js: -------------------------------------------------------------------------------- 1 | // Taken from https://github.com/Kechinator/jsvm/blob/9d735425197a60fbd88eb009da085fb9540f0ff9/vm/vm.js#L16 2 | const { Opcodes } = require('../constants'); 3 | 4 | module.exports = class VirtualMachine { 5 | constructor(bytecode, labelMap, pointerMap) { 6 | this.bytecode = bytecode; // raw bytecode 7 | this.instructionPointer = 0; 8 | this.stack = []; // our stack 9 | this.labelMap = labelMap; // mapping of label -> position 10 | this.pointerMap = pointerMap; 11 | this.pointerPos = 0; 12 | 13 | this.debug = false; 14 | } 15 | 16 | log(...a) { 17 | if (!this.debug) return; 18 | console.log('[DEBUG | VM]', ...a); 19 | } 20 | 21 | handleOpcode(code) { 22 | const getTwo = () => { 23 | return [this.stack.pop(), this.stack.pop()].reverse(); 24 | }; 25 | 26 | var a, b, val, label, condition, pos; 27 | 28 | switch (code) { 29 | // Simple arithmitic 30 | case Opcodes.ADD: 31 | [b, a] = getTwo(); 32 | this.stack.push(a + b); 33 | this.log(`ADD: ${a} + ${b} -> ${a + b}`); 34 | break; 35 | case Opcodes.SUB: 36 | [b, a] = getTwo(); 37 | this.stack.push(a - b); 38 | this.log(`SUB: ${a} - ${b} -> ${a - b}`); 39 | break; 40 | case Opcodes.MUL: 41 | [b, a] = getTwo(); 42 | this.stack.push(a * b); 43 | this.log(`MUL: ${a} * ${b} -> ${a * b}`); 44 | break; 45 | case Opcodes.DIV: 46 | [b, a] = getTwo(); 47 | this.stack.push(a / b); 48 | this.log(`DIV: ${a} / ${b} -> ${a / b}`); 49 | break; 50 | case Opcodes.MOD: 51 | [b, a] = getTwo(); 52 | this.stack.push(a % b); 53 | this.log(`MOD: ${a} % ${b} -> ${a % b}`); 54 | break; 55 | case Opcodes.NEG: 56 | a = this.stack.pop(); 57 | this.stack.push(!a); 58 | this.log(`NEG: !${a} -> ${!a}`); 59 | break; 60 | 61 | // Logical 62 | case Opcodes.EQUAL: 63 | [b, a] = getTwo(); 64 | this.stack.push(a == b); 65 | this.log(`EQUAL: ${a} == ${b} -> ${a == b}`); 66 | break; 67 | case Opcodes.NOT_EQUAL: 68 | [b, a] = getTwo(); 69 | this.stack.push(a != b); 70 | this.log(`NOT_EQUAL: ${a} != ${b} -> ${a != b}`); 71 | break; 72 | case Opcodes.STRICT_EQUAL: 73 | [b, a] = getTwo(); 74 | this.stack.push(a === b); 75 | this.log(`STRICT_EQUAL: ${a} === ${b} -> ${a === b}`); 76 | break; 77 | case Opcodes.STRICT_NOT_EQUAL: 78 | [b, a] = getTwo(); 79 | this.stack.push(a !== b); 80 | this.log(`STRICT_NOT_EQUAL: ${a} !== ${b} -> ${a !== b}`); 81 | break; 82 | case Opcodes.GREATER_THAN: 83 | [b, a] = getTwo(); 84 | this.stack.push(a > b); 85 | this.log(`GREATER_THAN: ${a} > ${b} -> ${a > b}`); 86 | break; 87 | case Opcodes.LESS_THAN: 88 | [b, a] = getTwo(); 89 | this.stack.push(a < b); 90 | this.log(`LESS_THAN: ${a} < ${b} -> ${a < b}`); 91 | break; 92 | case Opcodes.GREATER_THAN_EQUAL: 93 | [b, a] = getTwo(); 94 | this.stack.push(a >= b); 95 | this.log(`GREATER_THAN_EQUAL: ${a} >= ${b} -> ${a >= b}`); 96 | break; 97 | case Opcodes.LESS_THAN_EQUAL: 98 | [b, a] = getTwo(); 99 | this.stack.push(a <= b); 100 | this.log(`LESS_THAN_EQUAL: ${a} <= ${b} -> ${a <= b}`); 101 | break; 102 | 103 | // Bitwise 104 | case Opcodes.AND: 105 | [b, a] = getTwo(); 106 | this.stack.push(a & b); 107 | this.log(`AND: ${a} & ${b} -> ${a & b}`); 108 | break; 109 | case Opcodes.OR: 110 | [b, a] = getTwo(); 111 | this.stack.push(a | b); 112 | this.log(`OR: ${a} | ${b} -> ${a | b}`); 113 | break; 114 | case Opcodes.XOR: 115 | [b, a] = getTwo(); 116 | this.stack.push(a ^ b); 117 | this.log(`XOR: ${a} ^ ${b} -> ${a ^ b}`); 118 | break; 119 | case Opcodes.NOT: 120 | a = this.stack.pop(); 121 | this.stack.push(~a); 122 | this.log(`NOT: ~${a} -> ${~a}`); 123 | break; 124 | case Opcodes.LEFT_SHIFT: 125 | [b, a] = getTwo(); 126 | this.stack.push(a << b); 127 | this.log(`LEFT_SHIFT: ${a} << ${b} -> ${a << b}`); 128 | break; 129 | case Opcodes.RIGHT_SHIFT: 130 | [b, a] = getTwo(); 131 | this.stack.push(a >> b); 132 | this.log(`RIGHT_SHIFT: ${a} >> ${b} -> ${a >> b}`); 133 | break; 134 | case Opcodes.ZERO_LEFT_SHIFT: 135 | [b, a] = getTwo(); 136 | this.stack.push(a >>> b); 137 | this.log(`ZERO_LEFT_SHIFT: ${a} >>> ${b} -> ${a >>> b}`); 138 | break; 139 | 140 | // Stack operations 141 | case Opcodes.PUSH: 142 | this.instructionPointer = this.pointerMap[this.pointerPos++]; 143 | val = this.bytecode[this.instructionPointer]; 144 | this.stack.push(val); 145 | this.log(`PUSH: ${val}`); 146 | break; 147 | case Opcodes.JMP: 148 | this.instructionPointer = this.pointerMap[this.pointerPos++]; 149 | label = this.bytecode[this.instructionPointer]; 150 | pos = this.labelMap[label]; 151 | this.log(`JMP: to: ${label}, pos: ${pos}`); 152 | this.instructionPointer = pos; 153 | break; 154 | case Opcodes.JMP_IF: 155 | this.instructionPointer = this.pointerMap[this.pointerPos++]; 156 | label = this.bytecode[this.instructionPointer]; 157 | condition = this.stack.pop(); 158 | pos = this.labelMap[label]; 159 | this.log(`JMP_IF: to: ${label}, if: ${condition}, pos: ${pos}`); 160 | if (condition) this.instructionPointer = pos; 161 | break; 162 | 163 | // Object operations 164 | case Opcodes.PUSH_THIS: 165 | // TODO: This may cause conflicting issues, this is meant to make calls to non-defined functions like `console.log` work properly. 166 | this.stack.push(globalThis); 167 | this.log('PUSH_THIS'); 168 | break; 169 | case Opcodes.MEMBER_EXPRESSION: 170 | // ! get previous 2 things on the stack (eg: this, "console") 171 | [b, a] = getTwo(); 172 | // ! push to the stack their member expression (eg: this["console"]) 173 | this.stack.push(b[a]); 174 | this.log(`MEMBER_EXPRESSION: ${b}[${a}]`); 175 | break; 176 | case Opcodes.PUSH_ARRAY: 177 | this.stack.push([]); 178 | this.log('EMPTY_ARRAY'); 179 | break; 180 | case Opcodes.PUSH_OBJECT: 181 | this.stack.push({}); 182 | this.log('EMPTY_OBJECT'); 183 | break; 184 | case Opcodes.PUSH_TO_ARRAY: 185 | [b, a] = getTwo(); 186 | this.log(`PUSH_TO_ARRAY: ${JSON.stringify(b)}.push(${a})`); 187 | b.push(a); 188 | this.stack.push(b); 189 | break; 190 | 191 | // Other 192 | case Opcodes.EXECUTE_FUNCTION: 193 | // ! get previous 2 things on the stack (eg: this["console"]["log"], "test") 194 | [b, a] = getTwo(); 195 | this.log(`EXECUTE_FUNCTION: ${b}(${a})`); 196 | if (a.hasOwnProperty('length')) this.stack.push(b(...a)); 197 | else this.stack.push(b(a)); 198 | 199 | break; 200 | 201 | // VM operations 202 | case Opcodes.HLT: 203 | this.instructionPointer = -1; 204 | break; 205 | 206 | default: 207 | // console.log(code, this); 208 | throw new Error(`No handler for opcode ${code}`); 209 | } 210 | } 211 | 212 | run() { 213 | // We assume that the first byte is an opcode 214 | for (;;) { 215 | if (this.instructionPointer >= this.bytecode.length) { 216 | this.log('Done!'); 217 | this.log(this.stack); 218 | break; 219 | } 220 | if (this.pointerPos >= this.pointerMap.length) { 221 | this.log('Done! (I think)'); 222 | this.log(this.stack); 223 | break; 224 | } 225 | if (this.instructionPointer === -1) { 226 | this.log('Interrupted!'); 227 | break; 228 | } 229 | // this.log('Stack:', this.stack); 230 | // this.log(`Instruction Pointer: ${this.instructionPointer}`); 231 | // this.log(`pointer position: ${this.pointerPos}`); 232 | 233 | this.instructionPointer = this.pointerMap[this.pointerPos++]; 234 | const operation = this.bytecode[this.instructionPointer]; 235 | this.handleOpcode(operation); 236 | if (this.debug) console.log(' '); 237 | } 238 | } 239 | }; 240 | -------------------------------------------------------------------------------- /src/vm/parser.js: -------------------------------------------------------------------------------- 1 | const { Opcodes } = require('../constants'); 2 | 3 | const tryParseInt = (arg) => { 4 | try { 5 | const parsed = parseInt(arg); 6 | if (isNaN(parsed)) return arg; 7 | return parsed; 8 | } catch (e) { 9 | return arg; 10 | } 11 | }; 12 | 13 | module.exports = (asm) => { 14 | let program = []; 15 | let labelMap = {}; 16 | 17 | // Handle our ASM code line by line 18 | asm.split('\n').forEach((line) => { 19 | // if the line is empty or starts with a comment, skip 20 | if (!line || line.startsWith(';')) return; 21 | if (line.startsWith('@')) { 22 | labelMap[line.split('@')[1].split(' ')[0]] = program.length; 23 | return; 24 | } 25 | 26 | // Get the args of the line, handle comments 27 | let args = line 28 | .trim() 29 | .split(';')[0] 30 | .split(' ') 31 | .filter((el) => el.trim()); 32 | 33 | // Get the opcode 34 | const opcode = args.shift().trim(); 35 | 36 | // Handle unknown opcodes 37 | if (!(opcode in Opcodes)) 38 | throw new Error(`Parser: unknown opcode ${opcode}`); 39 | 40 | // Push opcode and args to program. Parse each arg as an int, if posible 41 | const parsed = args.map((a) => tryParseInt(a.trim())); 42 | console.log(line, '\t=>\t', Opcodes[opcode], ...parsed); 43 | program.push(Opcodes[opcode], ...parsed); 44 | }); 45 | 46 | return { 47 | program: program.filter((a) => a !== undefined), 48 | labelMap: labelMap, 49 | }; 50 | }; 51 | --------------------------------------------------------------------------------