@theqrl/hypc 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -11
- package/abi.js +17 -7
- package/bindings/compile.js +158 -31
- package/bindings/core.js +22 -6
- package/bindings/helpers.js +3 -4
- package/bindings/index.js +13 -6
- package/common/helpers.js +17 -3
- package/downloadCurrentVersion.js +20 -14
- package/formatters.js +2 -3
- package/hypc.js +89 -33
- package/hypjson.js +110 -1732
- package/linker.js +20 -0
- package/package.json +25 -25
- package/smtchecker.js +3 -1
- package/smtsolver.js +44 -17
- package/translate.js +1 -1
- package/verifyVersion.js +17 -7
- package/wrapper.js +305 -30
- package/build/clean.js +0 -4
- package/build/pack-publish-block.js +0 -10
- package/build/postbuild.js +0 -3
- package/test/abi.js +0 -77
- package/test/cli.js +0 -245
- package/test/compiler.js +0 -791
- package/test/index.js +0 -36
- package/test/linker.js +0 -88
- package/test/resources/fixtureAsmJson.json +0 -528
- package/test/smtcallback.js +0 -221
- package/test/smtchecker.js +0 -83
- package/test/translate.js +0 -83
- package/tsconfig.json +0 -25
package/linker.js
CHANGED
|
@@ -30,6 +30,23 @@ function libraryHash(fullyQualifiedLibraryName, length) {
|
|
|
30
30
|
function libraryHashPlaceholder(fullyQualifiedLibraryName) {
|
|
31
31
|
return `$${libraryHash(fullyQualifiedLibraryName, HASH_PLACEHOLDER_BODY_LENGTH)}$`;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Applies the compiler's address checksum to a hex address body: a letter is upper case when
|
|
35
|
+
* the matching nibble of SHAKE-256 (64 bytes) of the lower-case body is 8 or more.
|
|
36
|
+
* Mirrors `checksummedAddressBody` in hyperion's libhyputil/CommonData.cpp.
|
|
37
|
+
*/
|
|
38
|
+
function checksummedAddressBody(body) {
|
|
39
|
+
const lower = body.toLowerCase();
|
|
40
|
+
const hash = (0, js_sha3_1.shake256)(lower, ADDRESS_HEX_LENGTH * 4);
|
|
41
|
+
return [...lower].map((c, i) => (c >= 'a' && c <= 'f' && parseInt(hash[i], 16) >= 8 ? c.toUpperCase() : c)).join('');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Accepts an all-lower-case or all-upper-case body, or a mixed-case body with a valid checksum,
|
|
45
|
+
* as the compiler does for `settings.libraries`.
|
|
46
|
+
*/
|
|
47
|
+
function passesAddressChecksum(body) {
|
|
48
|
+
return body === body.toLowerCase() || body === body.toUpperCase() || body === checksummedAddressBody(body);
|
|
49
|
+
}
|
|
33
50
|
/**
|
|
34
51
|
* Finds all placeholders corresponding to the specified library label and replaces them
|
|
35
52
|
* with a concrete address. Works with both hex-encoded and binary bytecode as long as
|
|
@@ -105,6 +122,9 @@ function linkBytecode(bytecode, libraries) {
|
|
|
105
122
|
}
|
|
106
123
|
// remove address prefix
|
|
107
124
|
hexAddress = hexAddress.slice(ADDRESS_PREFIX.length);
|
|
125
|
+
if (!passesAddressChecksum(hexAddress)) {
|
|
126
|
+
throw new Error(`Invalid address checksum specified for ${libraryName}`);
|
|
127
|
+
}
|
|
108
128
|
bytecode = replacePlaceholder(bytecode, libraryName, hexAddress);
|
|
109
129
|
bytecode = replacePlaceholder(bytecode, libraryHashPlaceholder(libraryName), hexAddress);
|
|
110
130
|
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theqrl/hypc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Hyperion compiler",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"hypcjs": "hypc.js"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"*.js",
|
|
11
|
+
"bindings/*.js",
|
|
12
|
+
"common/*.js"
|
|
13
|
+
],
|
|
9
14
|
"scripts": {
|
|
10
15
|
"build": "tsc",
|
|
11
16
|
"postbuild": "node build/postbuild.js && cp README.md LICENSE dist/",
|
|
12
|
-
"lint": "eslint
|
|
13
|
-
"lint:fix": "eslint --fix
|
|
17
|
+
"lint": "eslint .",
|
|
18
|
+
"lint:fix": "eslint --fix .",
|
|
14
19
|
"updateBinary": "node build/clean.js && ts-node ./downloadCurrentVersion.ts && ts-node ./verifyVersion.ts",
|
|
15
20
|
"prepack": "node build/pack-publish-block.js",
|
|
16
21
|
"build:tarball": "npm run updateBinary && npm run build && BYPASS_SAFETY_CHECK=true npm pack ./dist",
|
|
@@ -19,8 +24,7 @@
|
|
|
19
24
|
"copyTestFiles": "cp -r ./test/resources ./dist/test/",
|
|
20
25
|
"pretest": "npm run lint && npm run build && npm run copyTestFiles",
|
|
21
26
|
"test": "cd dist && tape ./test/index.js --no-remote-versions",
|
|
22
|
-
"coverage": "nyc npm run test"
|
|
23
|
-
"coveralls": "npm run coverage && coveralls <coverage/lcov.info"
|
|
27
|
+
"coverage": "nyc npm run test"
|
|
24
28
|
},
|
|
25
29
|
"repository": {
|
|
26
30
|
"type": "git",
|
|
@@ -37,33 +41,29 @@
|
|
|
37
41
|
"url": "https://github.com/theQRL/hypc-js/issues"
|
|
38
42
|
},
|
|
39
43
|
"homepage": "https://github.com/theQRL/hypc-js#readme",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=22.12.0"
|
|
46
|
+
},
|
|
40
47
|
"dependencies": {
|
|
41
48
|
"command-exists": "^1.2.8",
|
|
42
|
-
"commander": "^
|
|
43
|
-
"follow-redirects": "^1.
|
|
44
|
-
"js-sha3": "0.
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"tmp": "0.0.33"
|
|
49
|
+
"commander": "^15.0.0",
|
|
50
|
+
"follow-redirects": "^1.16.2",
|
|
51
|
+
"js-sha3": "0.13.0",
|
|
52
|
+
"semver": "^7.8.5",
|
|
53
|
+
"tmp": "0.2.7"
|
|
48
54
|
},
|
|
49
55
|
"devDependencies": {
|
|
50
|
-
"@types/node": "^
|
|
51
|
-
"@types/semver": "^7.
|
|
52
|
-
"@types/tape": "^
|
|
56
|
+
"@types/node": "^22.12.0",
|
|
57
|
+
"@types/semver": "^7.8.0",
|
|
58
|
+
"@types/tape": "^5.8.1",
|
|
53
59
|
"@types/tmp": "^0.2.3",
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"eslint-config-standard": "^16.0.3",
|
|
59
|
-
"eslint-plugin-import": "^2.25.3",
|
|
60
|
-
"eslint-plugin-node": "^11.1.0",
|
|
61
|
-
"eslint-plugin-promise": "^5.1.1",
|
|
62
|
-
"nyc": "^15.1.0",
|
|
63
|
-
"tape": "^4.11.0",
|
|
60
|
+
"eslint": "^10.13.0",
|
|
61
|
+
"neostandard": "^0.14.1",
|
|
62
|
+
"nyc": "^18.0.0",
|
|
63
|
+
"tape": "^5.10.3",
|
|
64
64
|
"tape-spawn": "^1.4.2",
|
|
65
65
|
"ts-node": "^10.4.0",
|
|
66
|
-
"typescript": "
|
|
66
|
+
"typescript": "~6.0.2"
|
|
67
67
|
},
|
|
68
68
|
"nyc": {
|
|
69
69
|
"exclude": [
|
package/smtchecker.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
const helpers_1 = require("./common/helpers");
|
|
2
3
|
// This function checks the standard JSON output for auxiliaryInputRequested,
|
|
3
4
|
// where smtlib2queries represent the queries created by the SMTChecker.
|
|
4
5
|
// The function runs an SMT solver on each query and adjusts the input for
|
|
@@ -29,7 +30,8 @@ function smtCallback(solverFunction, solver) {
|
|
|
29
30
|
return { contents: result };
|
|
30
31
|
}
|
|
31
32
|
catch (err) {
|
|
32
|
-
|
|
33
|
+
// The compiler takes the error as a string, like the contents.
|
|
34
|
+
return { error: (0, helpers_1.describeException)(err) };
|
|
33
35
|
}
|
|
34
36
|
};
|
|
35
37
|
}
|
package/smtsolver.js
CHANGED
|
@@ -15,34 +15,51 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
const command_exists_1 = require("command-exists");
|
|
26
36
|
const child_process_1 = require("child_process");
|
|
27
37
|
const fs = __importStar(require("fs"));
|
|
28
38
|
const tmp = __importStar(require("tmp"));
|
|
29
39
|
// Timeout in ms.
|
|
30
40
|
const timeout = 10000;
|
|
41
|
+
// Solver answers are short; this only bounds a runaway solver.
|
|
42
|
+
const maxBuffer = 64 * 1024 * 1024;
|
|
31
43
|
const potentialSolvers = [
|
|
32
44
|
{
|
|
33
45
|
name: 'z3',
|
|
34
46
|
command: 'z3',
|
|
35
|
-
params: '-smt2 rlimit=20000000 rewriter.pull_cheap_ite=true fp.spacer.q3.use_qgen=true fp.spacer.mbqi=false fp.spacer.ground_pobs=false'
|
|
47
|
+
params: ['-smt2', 'rlimit=20000000', 'rewriter.pull_cheap_ite=true', 'fp.spacer.q3.use_qgen=true', 'fp.spacer.mbqi=false', 'fp.spacer.ground_pobs=false']
|
|
36
48
|
},
|
|
37
49
|
{
|
|
38
50
|
name: 'Eldarica',
|
|
39
51
|
command: 'eld',
|
|
40
|
-
params: '-horn -t:' + (timeout / 1000) // Eldarica takes timeout in seconds.
|
|
52
|
+
params: ['-horn', '-t:' + (timeout / 1000)] // Eldarica takes timeout in seconds.
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'cvc5',
|
|
56
|
+
command: 'cvc5',
|
|
57
|
+
params: ['--lang=smt2', '--tlimit=' + timeout]
|
|
41
58
|
},
|
|
42
59
|
{
|
|
43
60
|
name: 'cvc4',
|
|
44
61
|
command: 'cvc4',
|
|
45
|
-
params: '--lang=smt2 --tlimit=' + timeout
|
|
62
|
+
params: ['--lang=smt2', '--tlimit=' + timeout]
|
|
46
63
|
}
|
|
47
64
|
];
|
|
48
65
|
const solvers = potentialSolvers.filter(solver => (0, command_exists_1.sync)(solver.command));
|
|
@@ -59,18 +76,26 @@ function solve(query, solver) {
|
|
|
59
76
|
fs.writeFileSync(tmpFile.name, query);
|
|
60
77
|
let solverOutput;
|
|
61
78
|
try {
|
|
62
|
-
|
|
79
|
+
// No shell: the solver gets its arguments as an array. A caller's solver configuration may
|
|
80
|
+
// still give params as one string, as before; it is split on whitespace.
|
|
81
|
+
const params = typeof solver.params === 'string' ? solver.params.split(/\s+/).filter(Boolean) : solver.params;
|
|
82
|
+
solverOutput = (0, child_process_1.execFileSync)(solver.command, [...params, tmpFile.name], {
|
|
63
83
|
encoding: 'utf8',
|
|
64
|
-
maxBuffer
|
|
84
|
+
maxBuffer,
|
|
65
85
|
stdio: 'pipe',
|
|
66
|
-
timeout
|
|
86
|
+
timeout, // Enforce timeout on the process, since solvers can sometimes go around it.
|
|
87
|
+
killSignal: 'SIGKILL' // A solver can ignore SIGTERM.
|
|
67
88
|
}).toString();
|
|
68
89
|
}
|
|
69
90
|
catch (e) {
|
|
70
|
-
//
|
|
91
|
+
// The output was cut off at maxBuffer, so even an answer at its start cannot be trusted.
|
|
92
|
+
if (e.code === 'ENOBUFS') {
|
|
93
|
+
throw new Error(`Failed to solve SMT query. The solver wrote more than ${maxBuffer} bytes.`);
|
|
94
|
+
}
|
|
95
|
+
// execFileSync throws if the process times out or returns != 0.
|
|
71
96
|
// The latter might happen with z3 if the query asks for a model
|
|
72
97
|
// for an UNSAT formula. We can still use stdout.
|
|
73
|
-
solverOutput = e.stdout.toString();
|
|
98
|
+
solverOutput = (e.stdout || '').toString();
|
|
74
99
|
if (!solverOutput.startsWith('sat') &&
|
|
75
100
|
!solverOutput.startsWith('unsat') &&
|
|
76
101
|
!solverOutput.startsWith('unknown') &&
|
|
@@ -79,8 +104,10 @@ function solve(query, solver) {
|
|
|
79
104
|
throw new Error('Failed to solve SMT query. ' + e.toString());
|
|
80
105
|
}
|
|
81
106
|
}
|
|
82
|
-
|
|
83
|
-
|
|
107
|
+
finally {
|
|
108
|
+
// Trigger early manual cleanup
|
|
109
|
+
tmpFile.removeCallback();
|
|
110
|
+
}
|
|
84
111
|
return solverOutput;
|
|
85
112
|
}
|
|
86
113
|
module.exports = {
|
package/translate.js
CHANGED
package/verifyVersion.js
CHANGED
|
@@ -16,13 +16,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
16
16
|
}) : function(o, v) {
|
|
17
17
|
o["default"] = v;
|
|
18
18
|
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || function (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
26
36
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
37
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
38
|
};
|
package/wrapper.js
CHANGED
|
@@ -1,20 +1,161 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
|
-
const
|
|
38
|
+
const crypto_1 = require("crypto");
|
|
6
39
|
const follow_redirects_1 = require("follow-redirects");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const js_sha3_1 = require("js-sha3");
|
|
42
|
+
const path = __importStar(require("path"));
|
|
7
43
|
const formatters_1 = require("./formatters");
|
|
8
44
|
const helpers_1 = require("./common/helpers");
|
|
9
45
|
const bindings_1 = __importDefault(require("./bindings"));
|
|
10
46
|
const translate_1 = __importDefault(require("./translate"));
|
|
11
47
|
const Module = module.constructor;
|
|
12
|
-
|
|
13
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Wraps an Emscripten compiler module in the hypc-js API.
|
|
50
|
+
*
|
|
51
|
+
* A JavaScript exception that unwinds through the compiler's wasm frames, such as the RangeError
|
|
52
|
+
* of a stack overflow inside the compiler, leaves that module instance unable to compile (see
|
|
53
|
+
* `guarded` in bindings/compile.ts). `compile` throws the exception, and the next compilation
|
|
54
|
+
* runs on a fresh instance of the module, which `reload` returns. By default a module that
|
|
55
|
+
* `require` loaded is reloaded from its file, and `loadRemoteVersion` reloads from the downloaded
|
|
56
|
+
* source. Where neither applies, as in a browser or a bundle, pass a function that returns a fresh
|
|
57
|
+
* module. Without one, the same instance goes on once the compiler has discarded the abandoned
|
|
58
|
+
* compilation, which compilers from 0.3.0 on do in hyperion_reset(); with an older compiler, or
|
|
59
|
+
* after a WebAssembly trap, the next compilation returns an error that says the module cannot be
|
|
60
|
+
* used again.
|
|
61
|
+
*
|
|
62
|
+
* @param hypjson The Emscripten compiled Hyperion object.
|
|
63
|
+
* @param reload Returns a fresh instance of the same module.
|
|
64
|
+
*/
|
|
65
|
+
function wrapper(hypjson, reload = reloaderFor(hypjson)) {
|
|
66
|
+
let bindings = (0, bindings_1.default)(hypjson);
|
|
67
|
+
const { methodFlags } = bindings;
|
|
68
|
+
// Why no compilation can run any more, once that is certain: the instance cannot compile, and no
|
|
69
|
+
// fresh one can be loaded.
|
|
70
|
+
let unusable;
|
|
71
|
+
// Set while the instance goes on after discarding an abandoned compilation and its next output
|
|
72
|
+
// has not yet shown whether the compiler supports that; holds why it was needed.
|
|
73
|
+
let recoveringInPlace;
|
|
74
|
+
const withoutReplacement = (why) => `The compiler module cannot be used again: ${why}. Load a fresh module, or pass a function that does so as the second argument of setupMethods.`;
|
|
75
|
+
// The bindings of the current module instance, after an exception unwound through the previous
|
|
76
|
+
// one: of a fresh instance where one can be loaded, otherwise of the same instance after asking
|
|
77
|
+
// it to discard the abandoned compilation. Throws when neither is possible.
|
|
78
|
+
function usableBindings() {
|
|
79
|
+
if (unusable !== undefined) {
|
|
80
|
+
throw new Error(unusable);
|
|
81
|
+
}
|
|
82
|
+
if (!bindings.poisoned) {
|
|
83
|
+
return bindings;
|
|
84
|
+
}
|
|
85
|
+
const why = `an exception unwound through the compiler during an earlier compilation (${(0, helpers_1.describeException)(bindings.poisonedBy)})`;
|
|
86
|
+
if (reload !== undefined) {
|
|
87
|
+
try {
|
|
88
|
+
bindings = (0, bindings_1.default)(reload());
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
throw new Error(`The compiler module cannot be used again: ${why}, and reloading it failed: ${(0, helpers_1.describeException)(e)}`);
|
|
92
|
+
}
|
|
93
|
+
return bindings;
|
|
94
|
+
}
|
|
95
|
+
// No fresh instance can be loaded here, as in a browser or a bundle. A JavaScript exception
|
|
96
|
+
// leaves the instance's memory intact, and hyperion_reset() of compilers from 0.3.0 on
|
|
97
|
+
// discards the abandoned compilation's state, so the same instance can go on; whether this
|
|
98
|
+
// compiler does that shows in its next output (see compileMethod). What the abandoned
|
|
99
|
+
// compilation allocated stays allocated, since its destructors never ran and cannot be run
|
|
100
|
+
// now, which is why a fresh instance is preferred wherever one can be loaded. After a trap (a
|
|
101
|
+
// WebAssembly.RuntimeError, which is also what an Emscripten abort throws) the memory cannot
|
|
102
|
+
// be trusted.
|
|
103
|
+
if (typeof WebAssembly !== 'undefined' && bindings.poisonedBy instanceof WebAssembly.RuntimeError) {
|
|
104
|
+
unusable = withoutReplacement(`${why}, and the instance cannot be reused after a WebAssembly trap`);
|
|
105
|
+
throw new Error(unusable);
|
|
106
|
+
}
|
|
107
|
+
if ((0, helpers_1.isNil)(bindings.coreBindings.reset)) {
|
|
108
|
+
unusable = withoutReplacement(`${why}, and this compiler has no hyperion_reset() to discard it with`);
|
|
109
|
+
throw new Error(unusable);
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
bindings.coreBindings.reset();
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
unusable = withoutReplacement(`${why}, and hyperion_reset() failed afterwards: ${(0, helpers_1.describeException)(e)}`);
|
|
116
|
+
throw new Error(unusable);
|
|
117
|
+
}
|
|
118
|
+
bindings.poisoned = false;
|
|
119
|
+
bindings.poisonedBy = undefined;
|
|
120
|
+
recoveringInPlace = why;
|
|
121
|
+
return bindings;
|
|
122
|
+
}
|
|
123
|
+
// The compile method of that name, run on the current module instance, or null if the compiler
|
|
124
|
+
// lacks the method.
|
|
125
|
+
function compileMethod(name) {
|
|
126
|
+
if ((0, helpers_1.isNil)(bindings.compileBindings[name])) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return function (...args) {
|
|
130
|
+
let current;
|
|
131
|
+
try {
|
|
132
|
+
current = usableBindings();
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
return (0, formatters_1.formatFatalError)(e.message);
|
|
136
|
+
}
|
|
137
|
+
const output = current.compileBindings[name](...args);
|
|
138
|
+
if (recoveringInPlace !== undefined) {
|
|
139
|
+
const why = recoveringInPlace;
|
|
140
|
+
recoveringInPlace = undefined;
|
|
141
|
+
if (reportsStaleCompilerStack(output)) {
|
|
142
|
+
unusable = withoutReplacement(`${why}, and this compiler's hyperion_reset() does not discard the abandoned compilation (compilers from 0.3.0 on do)`);
|
|
143
|
+
return (0, formatters_1.formatFatalError)(unusable);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return output;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const compileBindings = {
|
|
150
|
+
compileJson: compileMethod('compileJson'),
|
|
151
|
+
compileJsonCallback: compileMethod('compileJsonCallback'),
|
|
152
|
+
compileJsonMulti: compileMethod('compileJsonMulti'),
|
|
153
|
+
compileStandard: compileMethod('compileStandard')
|
|
154
|
+
};
|
|
14
155
|
return {
|
|
15
|
-
version: coreBindings.version,
|
|
16
|
-
semver: coreBindings.versionToSemver,
|
|
17
|
-
license: coreBindings.license,
|
|
156
|
+
version: () => bindings.coreBindings.version(),
|
|
157
|
+
semver: () => bindings.coreBindings.versionToSemver(),
|
|
158
|
+
license: () => bindings.coreBindings.license(),
|
|
18
159
|
lowlevel: {
|
|
19
160
|
compileSingle: compileBindings.compileJson,
|
|
20
161
|
compileMulti: compileBindings.compileJsonMulti,
|
|
@@ -28,36 +169,170 @@ function wrapper(hypjson) {
|
|
|
28
169
|
nativeStandardJSON: methodFlags.compileJsonStandardSupported
|
|
29
170
|
},
|
|
30
171
|
compile: compileStandardWrapper.bind(this, compileBindings),
|
|
31
|
-
// Loads the compiler of the given version from
|
|
32
|
-
//
|
|
172
|
+
// Loads the compiler of the given version from binaries.theqrl.org instead of from the
|
|
173
|
+
// local filesystem, after checking it against list.json.
|
|
33
174
|
loadRemoteVersion,
|
|
34
175
|
// Use this if you want to add wrapper functions around the pure module.
|
|
35
176
|
setupMethods: wrapper
|
|
36
177
|
};
|
|
37
178
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
179
|
+
// Loads the source of a compiler module as require() does, in a Module of its own that is in
|
|
180
|
+
// neither require.cache nor module.children, so that an instance can be dropped once it is no
|
|
181
|
+
// longer usable. Emscripten's Node support adds a process-wide `unhandledRejection` listener on
|
|
182
|
+
// every load, which would accumulate and keep every instance alive; the listeners a load adds are
|
|
183
|
+
// removed again.
|
|
184
|
+
function loadModuleSource(source, filename) {
|
|
185
|
+
const events = ['uncaughtException', 'unhandledRejection'];
|
|
186
|
+
const listenersBefore = events.map(event => process.listeners(event));
|
|
187
|
+
try {
|
|
188
|
+
const hypjson = new Module(filename);
|
|
189
|
+
hypjson.filename = filename;
|
|
190
|
+
hypjson.paths = Module._nodeModulePaths(path.dirname(filename));
|
|
191
|
+
hypjson._compile(source, filename);
|
|
192
|
+
hypjson.loaded = true;
|
|
193
|
+
return hypjson.exports;
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
events.forEach((event, i) => {
|
|
197
|
+
for (const listener of process.listeners(event)) {
|
|
198
|
+
if (!listenersBefore[i].includes(listener)) {
|
|
199
|
+
process.removeListener(event, listener);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// A function that loads a fresh instance of a module that require() loaded, found through
|
|
206
|
+
// require.cache; undefined where there is no such record, as in a browser or a bundle.
|
|
207
|
+
function reloaderFor(hypjson) {
|
|
208
|
+
const cache = typeof require === 'function' ? require.cache : undefined;
|
|
209
|
+
if (!cache) {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
const record = Object.keys(cache)
|
|
213
|
+
.map(key => cache[key])
|
|
214
|
+
.find(entry => entry && entry.exports === hypjson && typeof entry.filename === 'string');
|
|
215
|
+
if (!record) {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
return () => loadModuleSource(fs.readFileSync(record.filename, 'utf8'), record.filename);
|
|
219
|
+
}
|
|
220
|
+
// True if the compiler's output reports the CompilerStack of an abandoned compilation, which is
|
|
221
|
+
// what an instance whose hyperion_reset() did not discard it answers to every compilation.
|
|
222
|
+
function reportsStaleCompilerStack(output) {
|
|
223
|
+
let parsed;
|
|
224
|
+
try {
|
|
225
|
+
parsed = JSON.parse(output);
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
const errors = parsed && parsed.errors;
|
|
231
|
+
return Array.isArray(errors) && errors.some(error => {
|
|
232
|
+
// Compilers before 0.4.x reported errors as strings.
|
|
233
|
+
const message = typeof error === 'string' ? error : error && error.message;
|
|
234
|
+
const internal = typeof error === 'string' || (error && error.type === 'InternalCompilerError');
|
|
235
|
+
return internal && typeof message === 'string' && message.includes('You shall not have another CompilerStack aside me.');
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
const BINARIES_HOST = 'binaries.theqrl.org';
|
|
239
|
+
const BINARIES_URL = `https://${BINARIES_HOST}/bin/`;
|
|
240
|
+
// list.json is a few kilobytes and a compiler build about 9 MB.
|
|
241
|
+
const MAX_LIST_BYTES = 1024 * 1024;
|
|
242
|
+
const MAX_BUILD_BYTES = 64 * 1024 * 1024;
|
|
243
|
+
// Follow redirects only to https URLs on the binaries host.
|
|
244
|
+
function beforeRedirect(options) {
|
|
245
|
+
if (options.protocol !== 'https:' || options.hostname !== BINARIES_HOST) {
|
|
246
|
+
throw new Error(`Refusing to follow a redirect to ${options.protocol}//${options.hostname}.`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Calls back once, with an error or the complete body of a 200 response.
|
|
250
|
+
function download(url, maxBytes, callback) {
|
|
251
|
+
let done = false;
|
|
252
|
+
const finish = (error, body) => {
|
|
253
|
+
if (!done) {
|
|
254
|
+
done = true;
|
|
255
|
+
callback(error, body);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
follow_redirects_1.https.get(url, { beforeRedirect, maxRedirects: 5 }, response => {
|
|
42
259
|
if (response.statusCode !== 200) {
|
|
43
|
-
|
|
260
|
+
response.resume();
|
|
261
|
+
finish(new Error(`Error retrieving ${url}: ${response.statusCode} ${response.statusMessage}`));
|
|
262
|
+
return;
|
|
44
263
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
264
|
+
const chunks = [];
|
|
265
|
+
let length = 0;
|
|
266
|
+
response.on('data', chunk => {
|
|
267
|
+
length += chunk.length;
|
|
268
|
+
if (length > maxBytes) {
|
|
269
|
+
response.destroy();
|
|
270
|
+
finish(new Error(`${url} is larger than ${maxBytes} bytes.`));
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
chunks.push(chunk);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
response.on('end', () => finish(null, Buffer.concat(chunks)));
|
|
277
|
+
response.on('error', finish);
|
|
278
|
+
}).on('error', finish);
|
|
279
|
+
}
|
|
280
|
+
// The list.json entry for "latest", a release such as "0.2.0", or a build such as
|
|
281
|
+
// "v0.2.0+commit.e36a0aea".
|
|
282
|
+
function findBuild(list, versionString) {
|
|
283
|
+
const releases = list.releases || {};
|
|
284
|
+
const buildPath = versionString === 'latest'
|
|
285
|
+
? releases[list.latestRelease]
|
|
286
|
+
: releases[versionString] || `hypjson-${versionString}.js`;
|
|
287
|
+
const build = (list.builds || []).find(entry => entry.path === buildPath);
|
|
288
|
+
if (!build ||
|
|
289
|
+
!/^hypjson-v[0-9A-Za-z.+-]+\.js$/.test(build.path) ||
|
|
290
|
+
typeof build.keccak256 !== 'string' ||
|
|
291
|
+
typeof build.sha256 !== 'string') {
|
|
292
|
+
throw new Error(`Version ${versionString} is not in ${BINARIES_URL}list.json.`);
|
|
293
|
+
}
|
|
294
|
+
return build;
|
|
295
|
+
}
|
|
296
|
+
// Downloads a compiler build and runs it only if its keccak256 and sha256 match list.json.
|
|
297
|
+
function loadRemoteVersion(versionString, callback) {
|
|
298
|
+
download(BINARIES_URL + 'list.json', MAX_LIST_BYTES, (listError, listBody) => {
|
|
299
|
+
if (listError) {
|
|
300
|
+
callback(listError);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
let build;
|
|
304
|
+
try {
|
|
305
|
+
build = findBuild(JSON.parse(listBody.toString('utf8')), versionString);
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
callback(e);
|
|
309
|
+
return;
|
|
58
310
|
}
|
|
59
|
-
|
|
60
|
-
|
|
311
|
+
download(BINARIES_URL + build.path, MAX_BUILD_BYTES, (error, body) => {
|
|
312
|
+
if (error) {
|
|
313
|
+
callback(error);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const keccak = '0x' + (0, js_sha3_1.keccak256)(body);
|
|
317
|
+
const sha256 = '0x' + (0, crypto_1.createHash)('sha256').update(body).digest('hex');
|
|
318
|
+
if (keccak !== build.keccak256.toLowerCase() || sha256 !== build.sha256.toLowerCase()) {
|
|
319
|
+
callback(new Error(`${build.path} does not match the keccak256 and sha256 in list.json, so it was not run.`));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
let hypc;
|
|
323
|
+
try {
|
|
324
|
+
// The source stays referenced so that a fresh instance can be loaded if an exception
|
|
325
|
+
// unwinds through this one (see wrapper).
|
|
326
|
+
const source = body.toString('utf8');
|
|
327
|
+
const load = () => loadModuleSource(source, build.path);
|
|
328
|
+
hypc = wrapper(load(), load);
|
|
329
|
+
}
|
|
330
|
+
catch (e) {
|
|
331
|
+
callback(e);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
callback(null, hypc);
|
|
335
|
+
});
|
|
61
336
|
});
|
|
62
337
|
}
|
|
63
338
|
// Expects a Standard JSON I/O but supports old compilers
|
|
@@ -87,12 +362,12 @@ function compileStandardWrapper(compile, inputRaw, readCallback) {
|
|
|
87
362
|
}
|
|
88
363
|
// Try to wrap around old versions
|
|
89
364
|
if (!(0, helpers_1.isNil)(compile.compileJsonCallback)) {
|
|
90
|
-
const inputJson = JSON.stringify({ sources
|
|
365
|
+
const inputJson = JSON.stringify({ sources });
|
|
91
366
|
const output = compile.compileJsonCallback(inputJson, optimize, readCallback);
|
|
92
367
|
return translateOutput(output, libraries);
|
|
93
368
|
}
|
|
94
369
|
if (!(0, helpers_1.isNil)(compile.compileJsonMulti)) {
|
|
95
|
-
const output = compile.compileJsonMulti(JSON.stringify({ sources
|
|
370
|
+
const output = compile.compileJsonMulti(JSON.stringify({ sources }), optimize);
|
|
96
371
|
return translateOutput(output, libraries);
|
|
97
372
|
}
|
|
98
373
|
// Try our luck with an ancient compiler
|