initial version of MakeHardener
ADD
.eslintrc.js
+module.exports = {
+ extends: ['airbnb'],
+ env: {
+ es6: true,
+ mocha: true,
+ },
+ rules: {
+ 'implicit-arrow-linebreak': 'off',
+ 'function-paren-newline': 'off',
+ 'arrow-parens': 'off',
+ },
+};

ADD
.prettierrc.json
+{
+ "trailingComma": "all",
+ "singleQuote": true
+}

ADD
.travis.yml
+language: node_js
+node_js:
+ - "10"
+
+sudo: false
+
+install:
+ - npm install
+
+script:
+ - npm run format
+ - npm run lint
+ - npm run test

ADD
index.js
+// Adapted from SES/Caja - Copyright (C) 2011 Google Inc.
+// Copyright (C) 2018 Agoric
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// based upon:
+// https://github.com/google/caja/blob/master/src/com/google/caja/ses/startSES.js
+// https://github.com/google/caja/blob/master/src/com/google/caja/ses/repairES5.js
+// then copied from proposal-frozen-realms deep-freeze.js
+// then copied from SES/src/bundle/deepFreeze.js
+
+function makeHardener(...initialRoots) {
+ const { freeze, getOwnPropertyDescriptors, getPrototypeOf } = Object;
+ const { ownKeys } = Reflect;
+ // Objects that we won't freeze, either because we've frozen them already,
+ // or they were one of the initial roots (terminals)
+ const rootSet = new WeakSet(initialRoots);
+
+ function harden(root) {
+ const toFreeze = new Set();
+ const prototypes = new Map();
+
+ function traverse(val, path) {
+ // extend toFreeze and prototypes, and recurse
+ if (Object(val) !== val) {
+ // ignore primitives
+ return;
+ }
+ const type = typeof val;
+ if (type !== 'object' && type !== 'function') {
+ // future proof: break until someone figures out what it should do
+ throw new TypeError(`Unexpected typeof: ${type}`);
+ }
+ if (rootSet.has(val) || toFreeze.has(val)) {
+ // Ignore if this is an exit, or we've already visited it
+ return;
+ }
+ // console.log(`adding ${val} to toFreeze`, val);
+ toFreeze.add(val);
+ // console.log(`adding ${getPrototypeOf(val)} to prototypes under ${path}`);
+ prototypes.set(getPrototypeOf(val), path);
+
+ // TODO: Immediately freeze the object to ensure reactive objects such
+ // as proxies won't add properties during traversal, before they get
+ // frozen. (this is at odds with our goal of not freezing anything if a
+ // prototype is not already in the rootSet)
+
+ // now walk the enumerable properties
+ const descs = getOwnPropertyDescriptors(val);
+ ownKeys(descs).forEach(name => {
+ // todo: getOwnPropertyDescriptors is guaranteed to return well-formed
+ // descriptors, but they still inherit from Object.prototype. If
+ // someone has poisoned Object.prototype to add 'value' or 'get'
+ // properties, then a simple 'if ("value" in desc)' or 'desc.value'
+ // test could be confused. We use hasOwnProperty to be sure about
+ // whether 'value' is present or not, which tells us for sure that this
+ // is a data property.
+ const desc = descs[name];
+ if ('value' in desc) {
+ // todo uncurried form
+ traverse(desc.value, `${path}.${name}`);
+ } else {
+ traverse(desc.get, `${path}.${name}[get]`);
+ traverse(desc.set, `${path}.${name}[set]`);
+ }
+ });
+ }
+
+ traverse(root, 'ROOT');
+
+ // console.log("rootSet", rootSet);
+ // console.log("prototype set:", prototypes);
+ // console.log("toFreeze set:", toFreeze);
+ prototypes.forEach((path, p) => {
+ if (!rootSet.has(p)) {
+ throw new TypeError(
+ `prototype ${p} of ${path} is not already in the rootSet`,
+ );
+ }
+ });
+
+ toFreeze.forEach(val => {
+ freeze(val);
+ rootSet.add(val);
+ });
+
+ return root;
+ }
+
+ return harden;
+}
+
+module.exports = { makeHardener };

ADD
package.json
+{
+ "name": "@agoric/make-hardener",
+ "version": "0.0.1",
+ "description": "Create a 'hardener' which freezes the API surface of a set of objects",
+ "main": "index.js",
+ "scripts": {
+ "test": "node test/test.js",
+ "format": "prettier --write '**/*.{js,jsx}'",
+ "lint": "eslint '**/*.{js,jsx}'"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/Agoric/MakeHardener.git"
+ },
+ "author": "Agoric",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/Agoric/MakeHardener/issues"
+ },
+ "homepage": "https://github.com/Agoric/MakeHardener#readme",
+ "dependencies": {},
+ "devDependencies": {
+ "eslint": "^5.3.0",
+ "eslint-config-airbnb": "^17.1.0",
+ "eslint-plugin-import": "^2.16.0",
+ "eslint-plugin-jsx-a11y": "^6.2.1",
+ "eslint-plugin-react": "^7.12.4",
+ "husky": "^1.3.1",
+ "lint-staged": "^8.1.3",
+ "prettier": "1.16.4",
+ "tape": "^4.9.2"
+ },
+ "lint-staged": {
+ "*.js": [
+ "prettier --write",
+ "eslint",
+ "git add"
+ ],
+ "*.md": [
+ "prettier --write",
+ "git add"
+ ]
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ },
+ "directories": {
+ "test": "test"
+ },
+ "keywords": [
+ "integer",
+ "int",
+ "overflow"
+ ]
+}

ADD
test/test.js
+const test = require('tape');
+const { makeHardener } = require('../index.js');
+
+test('makeHardener', t => {
+ const h = makeHardener(Object.prototype);
+ const o = { a: {} };
+ t.equal(h(o), o);
+ t.ok(Object.isFrozen(o));
+ t.ok(Object.isFrozen(o.a));
+ t.end();
+});
+
+test('do not freeze roots', t => {
+ const parent = { I_AM_YOUR: 'parent' };
+ const h = makeHardener(parent, Object.prototype);
+ const o = { a: {} };
+ Object.setPrototypeOf(o, parent);
+ t.equal(h(o), o);
+ t.ok(Object.isFrozen(o));
+ t.ok(Object.isFrozen(o.a));
+ t.notOk(Object.isFrozen(parent));
+ t.end();
+});
+
+test('complain about prototype not in roots', t => {
+ const parent = { I_AM_YOUR: 'parent' };
+ // at least one prototype is missing in each hardener
+ const h1 = makeHardener(Object.prototype);
+ const h2 = makeHardener(parent);
+ const o = { a: {} };
+ Object.setPrototypeOf(o, parent);
+ t.throws(() => h1(o), TypeError);
+ t.throws(() => h2(o), TypeError);
+ // and nothing should be frozen
+ t.notOk(Object.isFrozen(o));
+ t.notOk(Object.isFrozen(o.a));
+ t.notOk(Object.isFrozen(parent));
+ t.end();
+});

initial version of MakeHardener