@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/README.md
CHANGED
|
@@ -12,6 +12,8 @@ To use the latest stable version of the Hyperion compiler via Node.js you can in
|
|
|
12
12
|
npm install @theqrl/hypc
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
It needs Node.js 22.12 or later.
|
|
16
|
+
|
|
15
17
|
### Usage on the Command-Line
|
|
16
18
|
|
|
17
19
|
If this package is installed globally (`npm install -g @theqrl/hypc`), a command-line tool called `hypcjs` will be available.
|
|
@@ -30,6 +32,14 @@ Use the ``--base-path`` and ``--include-path`` options to describe the layout of
|
|
|
30
32
|
``--base-path`` represents the root of your own source tree while ``--include-path`` allows you to
|
|
31
33
|
specify extra locations containing external code (e.g. libraries installed with a package manager).
|
|
32
34
|
|
|
35
|
+
As in `hypc`, the import callback only reads files inside the base path (the working directory
|
|
36
|
+
when ``--base-path`` is not given), the include paths and the directories of the files given on
|
|
37
|
+
the command line. Symbolic links are resolved first. Any other import fails with
|
|
38
|
+
`File outside of allowed directories.`
|
|
39
|
+
If the working directory is the root of the filesystem (or of a drive on Windows) and
|
|
40
|
+
``--base-path`` is not given, the working directory is not allowed, since every readable file
|
|
41
|
+
would then be importable. Pass ``--base-path`` explicitly if you really need it.
|
|
42
|
+
|
|
33
43
|
Note: ensure that all the files you specify on the command line are located inside the base path or
|
|
34
44
|
one of the include paths.
|
|
35
45
|
The compiler refers to files from outside of these directories using absolute paths.
|
|
@@ -61,6 +71,11 @@ as a string. It cannot be used together with callback-based, asynchronous,
|
|
|
61
71
|
filesystem access. A workaround is to collect the names of dependencies, return
|
|
62
72
|
an error, and keep re-running the compiler until all of them are resolved.
|
|
63
73
|
|
|
74
|
+
A callback that throws, or that returns anything but an object with a string ``contents`` or a
|
|
75
|
+
string ``error``, does not abort the compilation: the compiler reports it as the import error, for
|
|
76
|
+
example ``Source "a.hyp" not found: Callback threw Error: ENOENT: no such file or directory``. An
|
|
77
|
+
``error`` that is not a string, such as an ``Error`` object, is reported as the error it describes.
|
|
78
|
+
|
|
64
79
|
#### Example usage without the import callback
|
|
65
80
|
|
|
66
81
|
Example:
|
|
@@ -149,7 +164,7 @@ return either an error or the result from the solver. A default
|
|
|
149
164
|
``smtchecker.js`` which exports the ``smtCallback`` function that takes 1) a
|
|
150
165
|
function that takes queries and returns the solving result, and 2) a solver
|
|
151
166
|
configuration object. The module ``smtsolver.js`` has a few predefined solver
|
|
152
|
-
configurations, and relies on Z3, Eldarica or
|
|
167
|
+
configurations, and relies on Z3, Eldarica, cvc5 or cvc4 being installed locally. It
|
|
153
168
|
exports the list of locally found solvers and a function that invokes a given
|
|
154
169
|
solver.
|
|
155
170
|
|
|
@@ -190,6 +205,18 @@ var output = JSON.parse(
|
|
|
190
205
|
The assertion is clearly false, and an ``assertion failure`` warning
|
|
191
206
|
should be returned, together with a counterexample.
|
|
192
207
|
|
|
208
|
+
#### When an exception unwinds through the compiler
|
|
209
|
+
|
|
210
|
+
`compile` throws when a JavaScript exception unwinds through the compiler itself, for example the
|
|
211
|
+
`RangeError` of a stack overflow on deeply nested source. The compiler's cleanup does not run then,
|
|
212
|
+
so the module instance the exception unwound through cannot compile again, and the next call to
|
|
213
|
+
`compile` loads a fresh instance of the module, or, where none can be loaded, goes on with the same
|
|
214
|
+
instance once the compiler has discarded the abandoned compilation. This needs nothing from the
|
|
215
|
+
caller for the bundled compiler and for one from `loadRemoteVersion`; for a module given to
|
|
216
|
+
`setupMethods`, see [Using a Legacy Version](#using-a-legacy-version). Going on with the same
|
|
217
|
+
instance keeps the memory of the abandoned compilation allocated, since the compiler cannot free
|
|
218
|
+
what the exception interrupted; a fresh instance does not.
|
|
219
|
+
|
|
193
220
|
#### Low-level API
|
|
194
221
|
|
|
195
222
|
The low-level API is as follows:
|
|
@@ -228,33 +255,50 @@ In order to compile contracts using a specific version of Hyperion, the `hypc.lo
|
|
|
228
255
|
You can also load the "binary" manually and use `setupMethods` to create the familiar wrapper functions described above:
|
|
229
256
|
`var hypc = hypc.setupMethods(require("/my/local/hypjson.js"))`.
|
|
230
257
|
|
|
231
|
-
|
|
258
|
+
When an exception unwinds through such a module (see above), `setupMethods` loads a fresh instance
|
|
259
|
+
from the module's file if `require` loaded it. Otherwise pass a function that returns a fresh
|
|
260
|
+
module as the second argument, `hypc.setupMethods(hypjson, loadHypjson)`. Without one, as in a
|
|
261
|
+
browser, the same instance goes on after `hyperion_reset()` has discarded the abandoned
|
|
262
|
+
compilation, which compilers from 0.3.0 on support; with an older compiler, or after a WebAssembly
|
|
263
|
+
trap, the next `compile` returns an error that says the module cannot be used again. Each
|
|
264
|
+
compilation the same instance goes on from leaves its memory allocated in the instance, so a
|
|
265
|
+
long-lived page that sees `compile` throw more than rarely should load a fresh module itself.
|
|
266
|
+
|
|
267
|
+
### Using the Latest Release
|
|
232
268
|
|
|
233
|
-
|
|
269
|
+
`loadRemoteVersion('latest', …)` loads the latest release in the
|
|
270
|
+
[release list](https://binaries.theqrl.org/bin/list.json), which can be newer than the compiler that
|
|
271
|
+
your installed copy of this package bundles.
|
|
234
272
|
|
|
235
273
|
```javascript
|
|
236
274
|
var hypc = require('@theqrl/hypc');
|
|
237
275
|
|
|
238
|
-
// getting the
|
|
239
|
-
hypc.loadRemoteVersion('latest', function(err,
|
|
276
|
+
// getting the latest release
|
|
277
|
+
hypc.loadRemoteVersion('latest', function(err, hypcLatest) {
|
|
240
278
|
if (err) {
|
|
241
279
|
// An error was encountered, display and quit
|
|
242
280
|
} else {
|
|
243
|
-
// NOTE: Use `
|
|
281
|
+
// NOTE: Use `hypcLatest` here with the same interface `hypc` has
|
|
244
282
|
// For example:
|
|
245
|
-
const output =
|
|
283
|
+
const output = hypcLatest.compile(/* ... */)
|
|
246
284
|
}
|
|
247
285
|
});
|
|
248
286
|
```
|
|
249
287
|
|
|
250
|
-
The version
|
|
251
|
-
|
|
252
|
-
|
|
288
|
+
The version is `latest`, a release such as `0.2.0`, or a build in the long format, which includes
|
|
289
|
+
the commit hash, such as `v0.2.0+commit.e36a0aea`. The [publicly available release list](https://binaries.theqrl.org/bin/list.json)
|
|
290
|
+
has every version.
|
|
253
291
|
|
|
254
292
|
```javascript
|
|
255
|
-
hypc.loadRemoteVersion('v0.
|
|
293
|
+
hypc.loadRemoteVersion('v0.2.0+commit.e36a0aea', function(err, hypcRemote) { /* ... */ });
|
|
256
294
|
```
|
|
257
295
|
|
|
296
|
+
`loadRemoteVersion` looks the version up in that list and downloads the build from
|
|
297
|
+
`binaries.theqrl.org`. It runs the build only if its keccak256 and sha256 match the list, and
|
|
298
|
+
otherwise passes an error to the callback. It follows redirects only to `https` URLs on the same
|
|
299
|
+
host, and stops reading a build larger than 64 MiB. A build that fails to load also reaches the
|
|
300
|
+
callback as an error.
|
|
301
|
+
|
|
258
302
|
### Linking Bytecode
|
|
259
303
|
|
|
260
304
|
When using libraries, the resulting bytecode will contain placeholders for the real addresses of the referenced libraries. These have to be updated, via a process called linking, before deploying the contract.
|
|
@@ -269,6 +313,9 @@ var linker = require('@theqrl/hypc/linker');
|
|
|
269
313
|
bytecode = linker.linkBytecode(bytecode, { MyLibrary: 'Q123456...' });
|
|
270
314
|
```
|
|
271
315
|
|
|
316
|
+
An address is `Q` followed by 128 hex digits. As in the compiler's `settings.libraries`, an address
|
|
317
|
+
in mixed case must carry the address checksum; one in all lower case or all upper case does not.
|
|
318
|
+
|
|
272
319
|
As of Hyperion 0.4.11 the compiler supports [standard JSON input and output](https://theqrl.org) which outputs a _link references_ map. This gives a map of library names to offsets in the bytecode to replace the addresses at. It also doesn't have the limitation on library file and contract name lengths.
|
|
273
320
|
|
|
274
321
|
There is a method available in the `linker` module called `findLinkReferences` which can find such link references in bytecode produced by an older compiler:
|
package/abi.js
CHANGED
|
@@ -15,13 +15,23 @@ 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 semver = __importStar(require("semver"));
|
|
26
36
|
function update(compilerVersion, abi) {
|
|
27
37
|
let hasConstructor = false;
|
package/bindings/compile.js
CHANGED
|
@@ -3,19 +3,66 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.setupCompile =
|
|
6
|
+
exports.setupCompile = setupCompile;
|
|
7
7
|
const assert_1 = __importDefault(require("assert"));
|
|
8
8
|
const helpers_1 = require("../common/helpers");
|
|
9
9
|
const helpers_2 = require("./helpers");
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* @param hypJson The Emscripten compiled Hyperion object.
|
|
12
|
+
* @param core The core bound Hyperion methods.
|
|
13
|
+
* @param onEscapedException Called with the exception when a JavaScript exception has unwound
|
|
14
|
+
* through the compiler's wasm frames; see `guarded` below.
|
|
15
|
+
*/
|
|
16
|
+
function setupCompile(hypJson, core, onEscapedException = () => { }) {
|
|
17
|
+
const guard = (method) => guarded(method, hypJson, onEscapedException);
|
|
11
18
|
return {
|
|
12
|
-
compileJson: bindCompileJson(hypJson),
|
|
13
|
-
compileJsonCallback: bindCompileJsonCallback(hypJson, core),
|
|
14
|
-
compileJsonMulti: bindCompileJsonMulti(hypJson),
|
|
15
|
-
compileStandard: bindCompileStandard(hypJson, core)
|
|
19
|
+
compileJson: bindCompileJson(hypJson, guard),
|
|
20
|
+
compileJsonCallback: bindCompileJsonCallback(hypJson, core, guard),
|
|
21
|
+
compileJsonMulti: bindCompileJsonMulti(hypJson, guard),
|
|
22
|
+
compileStandard: bindCompileStandard(hypJson, core, guard)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Wraps a bound compiler method so that an exception which unwinds through the compiler's wasm
|
|
27
|
+
* frames is reported before it propagates.
|
|
28
|
+
*
|
|
29
|
+
* The compiler is built with Emscripten's JavaScript exception handling: a C++ throw is a
|
|
30
|
+
* JavaScript throw of a number, which the `invoke_*` trampolines catch at every C++ try block so
|
|
31
|
+
* that landing pads (destructors, catch clauses) run. Any other JavaScript exception, such as the
|
|
32
|
+
* RangeError V8 raises when deeply recursive compiler code runs out of stack, or an exception
|
|
33
|
+
* thrown by an import callback, is rethrown by those trampolines without running the landing
|
|
34
|
+
* pads. The compiler's globals then stay as they were mid-compilation (the CompilerStack
|
|
35
|
+
* singleton guard is still taken, TypeProvider is not reset), and every later compilation on this
|
|
36
|
+
* module instance fails with "You shall not have another CompilerStack aside me." The wrapper
|
|
37
|
+
* (wrapper.ts) loads a fresh instance of the module in that case, or asks the compiler to discard
|
|
38
|
+
* the abandoned compilation where it cannot.
|
|
39
|
+
*
|
|
40
|
+
* Emscripten also restores the wasm stack pointer at every C++ try block such an exception passes,
|
|
41
|
+
* but not at the entry point, so the pointer is left below where the call started with nothing but
|
|
42
|
+
* dead frames above it. It is put back where it was, when the build exposes stackSave/stackRestore.
|
|
43
|
+
*/
|
|
44
|
+
function guarded(method, hypJson, onEscapedException) {
|
|
45
|
+
if ((0, helpers_1.isNil)(method))
|
|
46
|
+
return method;
|
|
47
|
+
return function (...args) {
|
|
48
|
+
const stackPointer = typeof hypJson.stackSave === 'function' ? hypJson.stackSave() : undefined;
|
|
49
|
+
try {
|
|
50
|
+
return method(...args);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
if (stackPointer !== undefined && typeof hypJson.stackRestore === 'function') {
|
|
54
|
+
try {
|
|
55
|
+
hypJson.stackRestore(stackPointer);
|
|
56
|
+
}
|
|
57
|
+
catch (restoreError) {
|
|
58
|
+
// Then the instance is beyond help; the wrapper will not go on with it.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
onEscapedException(e);
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
16
64
|
};
|
|
17
65
|
}
|
|
18
|
-
exports.setupCompile = setupCompile;
|
|
19
66
|
/**********************
|
|
20
67
|
* COMPILE
|
|
21
68
|
**********************/
|
|
@@ -24,18 +71,20 @@ exports.setupCompile = setupCompile;
|
|
|
24
71
|
* input (text), optimize (bool) -> output (jsontext)
|
|
25
72
|
*
|
|
26
73
|
* @param hypJson The Emscripten compiled Hyperion object.
|
|
74
|
+
* @param guard See `guarded`.
|
|
27
75
|
*/
|
|
28
|
-
function bindCompileJson(hypJson) {
|
|
29
|
-
return (0, helpers_2.bindHypcMethod)(hypJson, 'compileJSON', 'string', ['string', 'number'], null);
|
|
76
|
+
function bindCompileJson(hypJson, guard) {
|
|
77
|
+
return guard((0, helpers_2.bindHypcMethod)(hypJson, 'compileJSON', 'string', ['string', 'number'], null));
|
|
30
78
|
}
|
|
31
79
|
/**
|
|
32
80
|
* Returns a binding to the hyperion compileJSONMulti method.
|
|
33
81
|
* input (jsontext), optimize (bool) -> output (jsontext)
|
|
34
82
|
*
|
|
35
83
|
* @param hypJson The Emscripten compiled Hyperion object.
|
|
84
|
+
* @param guard See `guarded`.
|
|
36
85
|
*/
|
|
37
|
-
function bindCompileJsonMulti(hypJson) {
|
|
38
|
-
return (0, helpers_2.bindHypcMethod)(hypJson, 'compileJSONMulti', 'string', ['string', 'number'], null);
|
|
86
|
+
function bindCompileJsonMulti(hypJson, guard) {
|
|
87
|
+
return guard((0, helpers_2.bindHypcMethod)(hypJson, 'compileJSONMulti', 'string', ['string', 'number'], null));
|
|
39
88
|
}
|
|
40
89
|
/**
|
|
41
90
|
* Returns a binding to the hyperion compileJSONCallback method.
|
|
@@ -43,13 +92,14 @@ function bindCompileJsonMulti(hypJson) {
|
|
|
43
92
|
*
|
|
44
93
|
* @param hypJson The Emscripten compiled Hyperion object.
|
|
45
94
|
* @param coreBindings The core bound Hyperion methods.
|
|
95
|
+
* @param guard See `guarded`.
|
|
46
96
|
*/
|
|
47
|
-
function bindCompileJsonCallback(hypJson, coreBindings) {
|
|
48
|
-
const compileInternal = (0, helpers_2.bindHypcMethod)(hypJson, 'compileJSONCallback', 'string', ['string', 'number', 'number'], null);
|
|
97
|
+
function bindCompileJsonCallback(hypJson, coreBindings, guard) {
|
|
98
|
+
const compileInternal = guard((0, helpers_2.bindHypcMethod)(hypJson, 'compileJSONCallback', 'string', ['string', 'number', 'number'], null));
|
|
49
99
|
if ((0, helpers_1.isNil)(compileInternal))
|
|
50
100
|
return null;
|
|
51
101
|
return function (input, optimize, readCallback) {
|
|
52
|
-
return runWithCallbacks(hypJson, coreBindings, readCallback, compileInternal, [input, optimize]);
|
|
102
|
+
return runWithCallbacks(hypJson, coreBindings, readCallback, compileInternal, () => [input, optimize]);
|
|
53
103
|
};
|
|
54
104
|
}
|
|
55
105
|
/**
|
|
@@ -59,22 +109,35 @@ function bindCompileJsonCallback(hypJson, coreBindings) {
|
|
|
59
109
|
*
|
|
60
110
|
* @param hypJson The Emscripten compiled Hyperion object.
|
|
61
111
|
* @param coreBindings The core bound Hyperion methods.
|
|
112
|
+
* @param guard See `guarded`.
|
|
62
113
|
*/
|
|
63
|
-
function bindCompileStandard(hypJson, coreBindings) {
|
|
114
|
+
function bindCompileStandard(hypJson, coreBindings, guard) {
|
|
64
115
|
let boundFunctionStandard = null;
|
|
65
116
|
let boundFunctionHyperion = null;
|
|
66
117
|
// input (jsontext), callback (ptr) -> output (jsontext)
|
|
67
|
-
const compileInternal = (0, helpers_2.bindHypcMethod)(hypJson, 'compileStandard', 'string', ['string', 'number'], null);
|
|
68
|
-
// input (
|
|
69
|
-
|
|
118
|
+
const compileInternal = guard((0, helpers_2.bindHypcMethod)(hypJson, 'compileStandard', 'string', ['string', 'number'], null));
|
|
119
|
+
// input (ptr to text), callback (ptr), callback_context (ptr) -> output (jsontext)
|
|
120
|
+
//
|
|
121
|
+
// The input is passed as a pointer to a copy made with hyperion_alloc(), not as a 'string'
|
|
122
|
+
// argument: Emscripten's ccall copies a 'string' argument onto the wasm stack, whose size is
|
|
123
|
+
// fixed when the compiler is built (5 MB), so an input of more than about 1.3 million
|
|
124
|
+
// characters overflows it and the compilation crashes instead of running. The copy is freed by
|
|
125
|
+
// the reset() that runWithCallbacks makes after each compilation, like every other allocation
|
|
126
|
+
// made for the compiler. The output stays a 'string': it is read from the compiler's memory and
|
|
127
|
+
// freed by the same reset().
|
|
128
|
+
boundFunctionHyperion = guard((0, helpers_2.bindHypcMethod)(hypJson, 'hyperion_compile', 'string', ['number', 'number', 'number'], null));
|
|
70
129
|
if (!(0, helpers_1.isNil)(compileInternal)) {
|
|
71
130
|
boundFunctionStandard = function (input, readCallback) {
|
|
72
|
-
return runWithCallbacks(hypJson, coreBindings, readCallback, compileInternal, [input]);
|
|
131
|
+
return runWithCallbacks(hypJson, coreBindings, readCallback, compileInternal, () => [input]);
|
|
73
132
|
};
|
|
74
133
|
}
|
|
75
134
|
if (!(0, helpers_1.isNil)(boundFunctionHyperion)) {
|
|
76
135
|
boundFunctionStandard = function (input, callbacks) {
|
|
77
|
-
|
|
136
|
+
// The copy is made right before the guarded call, once the callback is set up, so that
|
|
137
|
+
// nothing that can fail runs between the copy and the compilation whose reset() frees it.
|
|
138
|
+
// It is still outside the guarded call: a failed allocation leaves nothing behind in the
|
|
139
|
+
// compiler, so it must not mark the instance poisoned.
|
|
140
|
+
return runWithCallbacks(hypJson, coreBindings, callbacks, boundFunctionHyperion, () => [coreBindings.allocCString(input)]);
|
|
78
141
|
};
|
|
79
142
|
}
|
|
80
143
|
return boundFunctionStandard;
|
|
@@ -87,17 +150,83 @@ function wrapCallbackWithKind(coreBindings, callback) {
|
|
|
87
150
|
return function (context, kind, data, contents, error) {
|
|
88
151
|
// Must be a null pointer.
|
|
89
152
|
(0, assert_1.default)(context === 0, 'Callback context must be null.');
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
153
|
+
// The compiler calls this from inside its wasm frames. An exception thrown here would unwind
|
|
154
|
+
// through them without running the compiler's cleanup and leave the module instance unusable
|
|
155
|
+
// (see `guarded`), so a callback that throws, or returns no usable object, is answered with an
|
|
156
|
+
// error instead, which the compiler reports like any other failed import or query.
|
|
157
|
+
let contentsString;
|
|
158
|
+
let errorString;
|
|
159
|
+
try {
|
|
160
|
+
const result = callback(coreBindings.copyFromCString(kind), coreBindings.copyFromCString(data));
|
|
161
|
+
({ contents: contentsString, error: errorString } = callbackResultStrings(result));
|
|
93
162
|
}
|
|
94
|
-
|
|
95
|
-
|
|
163
|
+
catch (e) {
|
|
164
|
+
contentsString = undefined;
|
|
165
|
+
errorString = `Callback threw ${(0, helpers_1.describeException)(e)}`;
|
|
166
|
+
}
|
|
167
|
+
if (contentsString !== undefined) {
|
|
168
|
+
coreBindings.copyToCString(contentsString, contents);
|
|
169
|
+
}
|
|
170
|
+
if (errorString !== undefined) {
|
|
171
|
+
coreBindings.copyToCString(errorString, error);
|
|
96
172
|
}
|
|
97
173
|
};
|
|
98
174
|
}
|
|
99
|
-
|
|
100
|
-
|
|
175
|
+
/**
|
|
176
|
+
* The strings to hand the compiler for what a callback returned: its `contents` and `error` where
|
|
177
|
+
* they are strings, otherwise an error that says what was returned. The compiler must get one of
|
|
178
|
+
* the two for every result: with neither, it reports "Callback not supported.", which hides that
|
|
179
|
+
* a callback was there and answered badly. An `error` that is not a string, such as an Error
|
|
180
|
+
* object, is reported as the error it describes.
|
|
181
|
+
*
|
|
182
|
+
* @param result What the callback returned.
|
|
183
|
+
*/
|
|
184
|
+
function callbackResultStrings(result) {
|
|
185
|
+
if ((0, helpers_1.isNil)(result) || !(0, helpers_1.isObject)(result)) {
|
|
186
|
+
return { error: `Callback returned ${describeValue(result)} instead of an object with contents or error.` };
|
|
187
|
+
}
|
|
188
|
+
const strings = {};
|
|
189
|
+
if (typeof result.contents === 'string') {
|
|
190
|
+
strings.contents = result.contents;
|
|
191
|
+
}
|
|
192
|
+
if (typeof result.error === 'string') {
|
|
193
|
+
strings.error = result.error;
|
|
194
|
+
}
|
|
195
|
+
else if (!(0, helpers_1.isNil)(result.error)) {
|
|
196
|
+
strings.error = (0, helpers_1.describeException)(result.error);
|
|
197
|
+
}
|
|
198
|
+
if (strings.contents === undefined && strings.error === undefined) {
|
|
199
|
+
strings.error = (0, helpers_1.isNil)(result.contents)
|
|
200
|
+
? 'Callback returned an object with neither contents nor error.'
|
|
201
|
+
: `Callback returned ${describeValue(result.contents)} as contents instead of a string.`;
|
|
202
|
+
}
|
|
203
|
+
return strings;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Names the kind of a value for an error message: `null`, `undefined`, `an array`, `a string`...
|
|
207
|
+
*
|
|
208
|
+
* @param value
|
|
209
|
+
*/
|
|
210
|
+
function describeValue(value) {
|
|
211
|
+
if ((0, helpers_1.isNil)(value))
|
|
212
|
+
return String(value);
|
|
213
|
+
if (Array.isArray(value))
|
|
214
|
+
return 'an array';
|
|
215
|
+
return `${typeof value === 'object' ? 'an' : 'a'} ${typeof value}`;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Runs a compile method with the given callbacks installed, and frees what the compilation
|
|
219
|
+
* allocated afterwards.
|
|
220
|
+
*
|
|
221
|
+
* @param hypJson The Emscripten compiled Hyperion object.
|
|
222
|
+
* @param coreBindings The core bound Hyperion methods.
|
|
223
|
+
* @param callbacks The callbacks object (or, before 0.6.0, the import callback) given by the caller.
|
|
224
|
+
* @param compile The guarded compile method.
|
|
225
|
+
* @param prepareArgs Returns the arguments that precede the callback and its context. It runs once
|
|
226
|
+
* the callback is installed, right before the call, so that an argument allocated in the
|
|
227
|
+
* compiler for the call cannot be left behind by a failure to install the callback.
|
|
228
|
+
*/
|
|
229
|
+
function runWithCallbacks(hypJson, coreBindings, callbacks, compile, prepareArgs) {
|
|
101
230
|
if (callbacks) {
|
|
102
231
|
(0, assert_1.default)(typeof callbacks === 'object', 'Invalid callback object specified.');
|
|
103
232
|
}
|
|
@@ -137,10 +266,8 @@ function runWithCallbacks(hypJson, coreBindings, callbacks, compile, args) {
|
|
|
137
266
|
const cb = coreBindings.addFunction(singleCallback, 'viiiii');
|
|
138
267
|
let output;
|
|
139
268
|
try {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
args.push(null);
|
|
143
|
-
output = compile(...args);
|
|
269
|
+
// The callback, then its context.
|
|
270
|
+
output = compile(...prepareArgs(), cb, null);
|
|
144
271
|
}
|
|
145
272
|
finally {
|
|
146
273
|
coreBindings.removeFunction(cb);
|
package/bindings/core.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.setupCore =
|
|
6
|
+
exports.setupCore = setupCore;
|
|
7
7
|
const helpers_1 = require("./helpers");
|
|
8
8
|
const translate_1 = __importDefault(require("../translate"));
|
|
9
9
|
const helpers_2 = require("../common/helpers");
|
|
@@ -19,6 +19,7 @@ function setupCore(hypJson) {
|
|
|
19
19
|
removeFunction: unboundRemoveFunction.bind(this, hypJson),
|
|
20
20
|
copyFromCString: unboundCopyFromCString.bind(this, hypJson),
|
|
21
21
|
copyToCString: unboundCopyToCString.bind(this, hypJson, core.alloc),
|
|
22
|
+
allocCString: unboundAllocCString.bind(this, hypJson, core.alloc),
|
|
22
23
|
// @ts-ignore
|
|
23
24
|
versionToSemver: versionToSemver(core.version())
|
|
24
25
|
};
|
|
@@ -27,7 +28,6 @@ function setupCore(hypJson) {
|
|
|
27
28
|
...helpers
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
|
-
exports.setupCore = setupCore;
|
|
31
31
|
/**********************
|
|
32
32
|
* Core Functions
|
|
33
33
|
**********************/
|
|
@@ -81,7 +81,7 @@ function bindReset(hypJson) {
|
|
|
81
81
|
/**
|
|
82
82
|
* Copy to a C string.
|
|
83
83
|
*
|
|
84
|
-
* Allocates memory using hypc's allocator.
|
|
84
|
+
* Allocates memory using hypc's allocator and returns the address of the copy.
|
|
85
85
|
*
|
|
86
86
|
* After 0.6.0:
|
|
87
87
|
* The duty is on hypc-js to free these pointers. We accomplish that by calling `reset` at the end.
|
|
@@ -89,13 +89,29 @@ function bindReset(hypJson) {
|
|
|
89
89
|
* @param hypJson The Emscripten compiled Hyperion object.
|
|
90
90
|
* @param alloc The memory allocation function.
|
|
91
91
|
* @param str The source string being copied to a C string.
|
|
92
|
-
* @param ptr The pointer location where the C string will be set.
|
|
93
92
|
*/
|
|
94
|
-
function
|
|
93
|
+
function unboundAllocCString(hypJson, alloc, str) {
|
|
95
94
|
const length = hypJson.lengthBytesUTF8(str);
|
|
96
95
|
const buffer = alloc(length + 1);
|
|
96
|
+
if (!buffer) {
|
|
97
|
+
// Writing through a NULL pointer would overwrite wasm memory at address 0.
|
|
98
|
+
throw new Error(`hyperion_alloc failed to allocate ${length + 1} bytes.`);
|
|
99
|
+
}
|
|
97
100
|
hypJson.stringToUTF8(str, buffer, length + 1);
|
|
98
|
-
|
|
101
|
+
return buffer;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Copy to a C string, and store its address through a pointer.
|
|
105
|
+
*
|
|
106
|
+
* Allocates memory as `unboundAllocCString` does.
|
|
107
|
+
*
|
|
108
|
+
* @param hypJson The Emscripten compiled Hyperion object.
|
|
109
|
+
* @param alloc The memory allocation function.
|
|
110
|
+
* @param str The source string being copied to a C string.
|
|
111
|
+
* @param ptr The pointer location where the C string will be set.
|
|
112
|
+
*/
|
|
113
|
+
function unboundCopyToCString(hypJson, alloc, str, ptr) {
|
|
114
|
+
hypJson.setValue(ptr, unboundAllocCString(hypJson, alloc, str), '*');
|
|
99
115
|
}
|
|
100
116
|
/**
|
|
101
117
|
* Wrapper over Emscripten's C String copying function (which can be different
|
package/bindings/helpers.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.bindHypcMethod = bindHypcMethod;
|
|
4
|
+
exports.bindHypcMethodWithFallbackFunc = bindHypcMethodWithFallbackFunc;
|
|
5
|
+
exports.getSupportedMethods = getSupportedMethods;
|
|
4
6
|
const helpers_1 = require("../common/helpers");
|
|
5
7
|
function bindHypcMethod(hypJson, method, returnType, args, defaultValue) {
|
|
6
8
|
if ((0, helpers_1.isNil)(hypJson[`_${method}`]) && defaultValue !== undefined) {
|
|
@@ -8,7 +10,6 @@ function bindHypcMethod(hypJson, method, returnType, args, defaultValue) {
|
|
|
8
10
|
}
|
|
9
11
|
return hypJson.cwrap(method, returnType, args);
|
|
10
12
|
}
|
|
11
|
-
exports.bindHypcMethod = bindHypcMethod;
|
|
12
13
|
function bindHypcMethodWithFallbackFunc(hypJson, method, returnType, args, fallbackMethod, finalFallback = undefined) {
|
|
13
14
|
const methodFunc = bindHypcMethod(hypJson, method, returnType, args, null);
|
|
14
15
|
if (!(0, helpers_1.isNil)(methodFunc)) {
|
|
@@ -16,7 +17,6 @@ function bindHypcMethodWithFallbackFunc(hypJson, method, returnType, args, fallb
|
|
|
16
17
|
}
|
|
17
18
|
return bindHypcMethod(hypJson, fallbackMethod, returnType, args, finalFallback);
|
|
18
19
|
}
|
|
19
|
-
exports.bindHypcMethodWithFallbackFunc = bindHypcMethodWithFallbackFunc;
|
|
20
20
|
function getSupportedMethods(hypJson) {
|
|
21
21
|
return {
|
|
22
22
|
licenseSupported: anyMethodExists(hypJson, 'hyperion_license'),
|
|
@@ -29,7 +29,6 @@ function getSupportedMethods(hypJson) {
|
|
|
29
29
|
compileJsonStandardSupported: anyMethodExists(hypJson, 'compileStandard', 'hyperion_compile')
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
exports.getSupportedMethods = getSupportedMethods;
|
|
33
32
|
function anyMethodExists(hypJson, ...names) {
|
|
34
33
|
return names.some(name => !(0, helpers_1.isNil)(hypJson[`_${name}`]));
|
|
35
34
|
}
|
package/bindings/index.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = setupBindings;
|
|
3
4
|
const core_1 = require("./core");
|
|
4
5
|
const helpers_1 = require("./helpers");
|
|
5
6
|
const compile_1 = require("./compile");
|
|
6
7
|
function setupBindings(hypJson) {
|
|
7
8
|
const coreBindings = (0, core_1.setupCore)(hypJson);
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
return {
|
|
11
|
-
methodFlags,
|
|
9
|
+
const bindings = {
|
|
10
|
+
methodFlags: (0, helpers_1.getSupportedMethods)(hypJson),
|
|
12
11
|
coreBindings,
|
|
13
|
-
compileBindings
|
|
12
|
+
compileBindings: null,
|
|
13
|
+
// Set, with the exception, once a JavaScript exception has unwound through the compiler's wasm
|
|
14
|
+
// frames. This module instance cannot compile after that; see `guarded` in ./compile.ts.
|
|
15
|
+
poisoned: false,
|
|
16
|
+
poisonedBy: undefined
|
|
14
17
|
};
|
|
18
|
+
bindings.compileBindings = (0, compile_1.setupCompile)(hypJson, coreBindings, (e) => {
|
|
19
|
+
bindings.poisoned = true;
|
|
20
|
+
bindings.poisonedBy = e;
|
|
21
|
+
});
|
|
22
|
+
return bindings;
|
|
15
23
|
}
|
|
16
|
-
exports.default = setupBindings;
|
package/common/helpers.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.isNil = isNil;
|
|
4
|
+
exports.isObject = isObject;
|
|
5
|
+
exports.describeException = describeException;
|
|
4
6
|
/**
|
|
5
7
|
* Returns true if and only if the value is null or undefined.
|
|
6
8
|
*
|
|
@@ -10,7 +12,6 @@ function isNil(value) {
|
|
|
10
12
|
// Uses == over === which compares both null and undefined.
|
|
11
13
|
return value == null;
|
|
12
14
|
}
|
|
13
|
-
exports.isNil = isNil;
|
|
14
15
|
/**
|
|
15
16
|
* Returns true if and only if the value is an object and not an array.
|
|
16
17
|
*
|
|
@@ -21,4 +22,17 @@ function isObject(value) {
|
|
|
21
22
|
// to confirm it's just an object.
|
|
22
23
|
return typeof value === 'object' && !Array.isArray(value);
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Returns the exception as a string for an error message, such as `Error: boom` or
|
|
27
|
+
* `RangeError: Maximum call stack size exceeded`.
|
|
28
|
+
*
|
|
29
|
+
* @param e The thrown value.
|
|
30
|
+
*/
|
|
31
|
+
function describeException(e) {
|
|
32
|
+
try {
|
|
33
|
+
return String(e);
|
|
34
|
+
}
|
|
35
|
+
catch (conversionError) {
|
|
36
|
+
return 'an exception that cannot be converted to a string';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -18,33 +18,39 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
18
18
|
}) : function(o, v) {
|
|
19
19
|
o["default"] = v;
|
|
20
20
|
});
|
|
21
|
-
var __importStar = (this && this.__importStar) || function (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
31
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
39
|
const fs = __importStar(require("fs"));
|
|
33
40
|
const follow_redirects_1 = require("follow-redirects");
|
|
34
|
-
const memorystream_1 = __importDefault(require("memorystream"));
|
|
35
41
|
const js_sha3_1 = require("js-sha3");
|
|
36
42
|
const pkg = require('./package.json');
|
|
37
43
|
function getVersionList(cb) {
|
|
38
44
|
console.log('Retrieving available version list...');
|
|
39
|
-
const mem = new memorystream_1.default(null, { readable: false });
|
|
40
45
|
follow_redirects_1.https.get('https://binaries.theqrl.org/bin/list.json', function (response) {
|
|
41
46
|
if (response.statusCode !== 200) {
|
|
42
47
|
console.log('Error downloading file: ' + response.statusCode);
|
|
43
48
|
process.exit(1);
|
|
44
49
|
}
|
|
45
|
-
|
|
50
|
+
const chunks = [];
|
|
51
|
+
response.on('data', chunk => chunks.push(chunk));
|
|
46
52
|
response.on('end', function () {
|
|
47
|
-
cb(
|
|
53
|
+
cb(Buffer.concat(chunks).toString());
|
|
48
54
|
});
|
|
49
55
|
});
|
|
50
56
|
}
|