Skip to content

Commit

Permalink
feat(eval): end-to-end metered evaluator
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Feb 11, 2020
1 parent d421bc5 commit db3acfd
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 198 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"packages/acorn-eventual-send",
"packages/eventual-send",
"packages/transform-eventual-send",
"packages/tame-metering",
"packages/transform-metering",
"packages/default-evaluate-options",
"packages/evaluate",
Expand Down
34 changes: 34 additions & 0 deletions packages/transform-metering/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@ The purpose of this package is to provide a loose, but deterministic way to inte

This technique is not airtight, but it is at least is a best approximation in the absence of an instrumented host platform.

## Quickstart

```js
import SES from 'ses';
import * as babelCore from '@babel/core';
import { makeMeteredEvaluator } from '@agoric/transform-metering';
import tameMetering from '@agoric/tame-metering';

// Override all the global objects with metered versions.
const setGlobalMeter = tameMetering();

const meteredEval = makeMeteredEvaluator({
// Needed for enabling metering of the global builtins.
setGlobalMeter,
// Needed for source transforms that prevent runaways.
babelCore,
// ({ transforms }) => { evaluate(src, endowments = {}) { [eval function] } }
makeEvaluator: SES.makeSESRootRealm, // TODO: change to new SES/Compartment API
});

// Now use the returned meteredEval: it should not throw.
const { exhausted, exceptionBox, returned } = meteredEval(untrustedSource, /* endowments */);
if (exhausted) {
console.log('the meter was exhausted');
}
if (exceptionBox) {
console.log('the source threw exception', exceptionBox[0]);
} else {
console.log('the source returned', returned);
}
```

# Implementation Details

## Meter types

There are three types of meters:
Expand Down
1 change: 1 addition & 0 deletions packages/transform-metering/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dependencies": {
"@agoric/harden": "^0.0.4",
"@agoric/nat": "^2.0.1",
"@agoric/tame-metering": "^1.0.0",
"re2": "^1.10.5"
},
"keywords": [],
Expand Down
5 changes: 1 addition & 4 deletions packages/transform-metering/src/constants.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
// These are the meter members of the meterId.
export const METER_ALLOCATE = 'a';
export const METER_COMPUTE = 'c';
export const METER_ENTER = 'e';
export const METER_LEAVE = 'l';
export * from '@agoric/tame-metering/src/constants';
export const METER_COMBINED = '*';

export const DEFAULT_METER_ID = '$h\u200d_meter';
Expand Down
115 changes: 0 additions & 115 deletions packages/transform-metering/src/endow.js

This file was deleted.

39 changes: 39 additions & 0 deletions packages/transform-metering/src/evaluator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { makeWithMeter } from './with';
import { makeMeterAndResetters } from './meter';
import { makeMeteringTransformer } from './transform';

export function makeMeteredEvaluator({
setGlobalMeter,
makeEvaluator,
babelCore,
maxima,
}) {
const [meter, reset] = makeMeterAndResetters(maxima);
const { meterId, meteringTransform } = makeMeteringTransformer(babelCore);
const transforms = [meteringTransform];
const { withMeter } = makeWithMeter(setGlobalMeter, meter);

const ev = makeEvaluator({ transforms });

return (src, endowments = {}) => {
// Reset all meters to their defaults.
Object.values(reset).forEach(r => r());
endowments[meterId] = meter;
let exhausted = true;
let returned;
let exceptionBox = false;
try {
// Evaluate the source with the meter.
returned = withMeter(() => ev.evaluate(src, { [meterId]: meter }));
exhausted = false;
} catch (e) {
exceptionBox = [e];
exhausted = reset.isExhausted();
}
return {
exhausted,
returned,
exceptionBox,
};
};
}
3 changes: 2 additions & 1 deletion packages/transform-metering/src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { makeMeteredEvaluator } from './evaluator';
export { makeMeter, makeMeterAndResetters } from './meter';
export { makeMeteringEndowments } from './endow';
export { makeMeteringTransformer } from './transform';
export { makeWithMeter } from './with';
14 changes: 12 additions & 2 deletions packages/transform-metering/src/meter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import * as c from './constants';

const { isArray } = Array;
const { getOwnPropertyDescriptors } = Object;
const { ceil } = Math;
const ObjectConstructor = Object;

// eslint-disable-next-line no-bitwise
const bigIntWord = typeof BigInt !== 'undefined' && BigInt(1 << 32);
Expand Down Expand Up @@ -64,7 +66,7 @@ export function makeAllocateMeter(maybeAbort, meter, allocateCounter = null) {
meter[c.METER_ENTER]();
maybeAbort();
let cost = 1;
if (value && Object(value) === value) {
if (value && ObjectConstructor(value) === value) {
// Either an array or an object with properties.
if (isArray(value)) {
// The size of the array. This property cannot be overridden.
Expand All @@ -83,7 +85,7 @@ export function makeAllocateMeter(maybeAbort, meter, allocateCounter = null) {
switch (t) {
case 'string':
// The size of the string, in approximate words.
cost += Math.ceil(value.length / 4);
cost += ceil(value.length / 4);
break;
case 'bigint': {
// Compute the number of words in the bigint.
Expand Down Expand Up @@ -183,6 +185,14 @@ export function makeMeterAndResetters(maxima = {}) {
}
};
const resetters = {
isExhausted() {
try {
maybeAbort();
return undefined;
} catch (e) {
return e;
}
},
allocate: makeResetter(allocateCounter),
stack: makeResetter(stackCounter),
compute: makeResetter(computeCounter),
Expand Down
23 changes: 19 additions & 4 deletions packages/transform-metering/src/transform.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import harden from '@agoric/harden';

import * as c from './constants';

// We'd like to import this, but RE2 is cjs
const RE2 = require('re2');

const METER_GENERATED = Symbol('meter-generated');

export function makeMeteringTransformer(
babelCore,
overrideParser = undefined,
overrideMeterId = c.DEFAULT_METER_ID,
overrideRegexpIdPrefix = c.DEFAULT_REGEXP_ID_PREFIX,
{
overrideParser = undefined,
overrideRegExp = harden(RE2),
overrideMeterId = c.DEFAULT_METER_ID,
overrideRegExpIdPrefix = c.DEFAULT_REGEXP_ID_PREFIX,
} = {},
) {
const parser = overrideParser
? overrideParser.parse || overrideParser
: babelCore.parseSync;
const meterId = overrideMeterId;
const regexpIdPrefix = overrideRegexpIdPrefix;
const regexpIdPrefix = overrideRegExpIdPrefix;
let regexpNumber = 0;

const meteringPlugin = regexpList => ({ types: t }) => {
Expand Down Expand Up @@ -167,10 +175,17 @@ const ${reid}=RegExp(${JSON.stringify(pattern)},${JSON.stringify(flags)});`);
? `(function(){${reSource}return ${maybeSource}})()`
: `${reSource}${maybeSource}`;

if (overrideRegExp) {
// By default, override with RE2, which protects against
// catastrophic backtracking.
endowments.RegExp = overrideRegExp;
}

// console.log('metered source:', actualSource);
return {
...ss,
ast,
endowments,
src: actualSource,
};
},
Expand Down
13 changes: 13 additions & 0 deletions packages/transform-metering/src/with.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function makeWithMeter(setGlobalMeter, defaultMeter = null) {
const withMeter = (thunk, newMeter = defaultMeter) => {
let oldMeter;
try {
oldMeter = setGlobalMeter(newMeter);
return thunk();
} finally {
setGlobalMeter(oldMeter);
}
};
const withoutMeter = thunk => withMeter(thunk, null);
return { withMeter, withoutMeter };
}
50 changes: 0 additions & 50 deletions packages/transform-metering/test/test-endow.js

This file was deleted.

Loading

0 comments on commit db3acfd

Please sign in to comment.