From 2688c09639cfd4cd78fe7c8809dcfef9e8868ac4 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 4 Nov 2018 19:52:53 +0000 Subject: [PATCH] Be stricter about permissible assertions; repair error in Structure instantiation which led to raw arrays sneaking in --- packages/core/src/bag.js | 2 +- packages/core/src/dataspace.js | 18 +++++- packages/core/src/skeleton.js | 5 +- packages/core/src/struct.js | 15 +++-- packages/core/test/test-skeleton.js | 90 +++++++++++++++-------------- 5 files changed, 75 insertions(+), 55 deletions(-) diff --git a/packages/core/src/bag.js b/packages/core/src/bag.js index 9bbce4a..917b605 100644 --- a/packages/core/src/bag.js +++ b/packages/core/src/bag.js @@ -77,7 +77,7 @@ const Bag = Immutable.Map; function fromSet(s) { return Bag().withMutations(function (b) { for (let v of Immutable.Set(s)) { - b = b.set(v, 1); + b = b.set(Immutable.fromJS(v), 1); } }); } diff --git a/packages/core/src/dataspace.js b/packages/core/src/dataspace.js index 04b304c..cdd2032 100644 --- a/packages/core/src/dataspace.js +++ b/packages/core/src/dataspace.js @@ -344,6 +344,20 @@ Actor.prototype.pendingPatch = function () { Actor.prototype.assert = function (a) { this.pendingPatch().adjust(a, +1); }; Actor.prototype.retract = function (a) { this.pendingPatch().adjust(a, -1); }; +Actor.prototype.adhocRetract = function (a) { + a = Immutable.fromJS(a); + if (this.adhocAssertions.change(a, -1, true) === Bag.PRESENT_TO_ABSENT) { + this.retract(a); + } +}; + +Actor.prototype.adhocAssert = function (a) { + a = Immutable.fromJS(a); + if (this.adhocAssertions.change(a, +1) === Bag.ABSENT_TO_PRESENT) { + this.assert(a); + } +}; + Actor.prototype.toString = function () { let s = 'Actor(' + this.id; if (this.name !== void 0) s = s + ',' + JSON.stringify(this.name); @@ -360,7 +374,7 @@ Patch.prototype.perform = function (ds, ac) { Patch.prototype.adjust = function (a, count) { var _net; - ({bag: this.changes, net: _net} = Bag.change(this.changes, a, count)); + ({bag: this.changes, net: _net} = Bag.change(this.changes, Immutable.fromJS(a), count)); }; function Message(body) { @@ -369,7 +383,7 @@ function Message(body) { Message.prototype.perform = function (ds, ac) { if (this.body !== void 0) { - ds.index.sendMessage(this.body); + ds.index.sendMessage(Immutable.fromJS(this.body)); } }; diff --git a/packages/core/src/skeleton.js b/packages/core/src/skeleton.js index a8f426d..d862d0a 100644 --- a/packages/core/src/skeleton.js +++ b/packages/core/src/skeleton.js @@ -249,7 +249,6 @@ function del_from_handler(h, vs) { Index.prototype.adjustAssertion = function(outerValue, delta) { let net; - outerValue = Immutable.fromJS(outerValue); ({bag: this.allAssertions, net: net} = Bag.change(this.allAssertions, outerValue, delta)); switch (net) { case Bag.ABSENT_TO_PRESENT: @@ -265,7 +264,7 @@ Index.prototype.addAssertion = function(v) { this.adjustAssertion(v, +1); }; Index.prototype.removeAssertion = function (v) { this.adjustAssertion(v, -1); }; Index.prototype.sendMessage = function(v) { - this.root.modify(Immutable.fromJS(v), ()=>{}, ()=>{}, (h, vs) => { + this.root.modify(v, ()=>{}, ()=>{}, (h, vs) => { h.callbacks.forEach((cb) => { cb(EVENT_MESSAGE, vs); return true; @@ -321,7 +320,7 @@ function analyzeAssertion(a) { } } - let skeleton = walk(Immutable.List(), Immutable.fromJS(a)); + let skeleton = walk(Immutable.List(), a); return { skeleton, constPaths, constVals, capturePaths }; } diff --git a/packages/core/src/struct.js b/packages/core/src/struct.js index de3b51f..f3c66fb 100644 --- a/packages/core/src/struct.js +++ b/packages/core/src/struct.js @@ -28,14 +28,14 @@ const __ = new $Special("wildcard"); /* wildcard marker */ function StructureType(label, arity) { this.label = label; this.arity = arity; - this.pattern = this.instantiate(Immutable.Repeat(__, arity).toArray()); + // this.pattern = this.instantiate(Immutable.Repeat(__, arity).toArray()); var self = this; this.ctor = function () { return self.instantiate(Array.prototype.slice.call(arguments)); }; this.ctor.meta = this; - this.ctor.pattern = this.pattern; + // this.ctor.pattern = this.pattern; this.ctor.isClassOf = function (v) { return self.isClassOf(v); }; } @@ -68,9 +68,10 @@ function Structure(meta, fields) { throw new Error("Structure: cannot instantiate meta "+JSON.stringify(meta.label)+ " expecting "+meta.arity+" fields with "+fields.length+" fields"); } + fields = fields.slice(0); this.meta = meta; this.length = meta.arity; - this.fields = fields.slice(0); + this.fields = fields; for (var i = 0; i < fields.length; i++) { this[i] = fields[i] = Immutable.fromJS(fields[i]); if (this[i] === void 0) { @@ -98,9 +99,11 @@ Structure.prototype.equals = function (other) { if (!(other instanceof Structure)) return false; if (!other.meta.equals(this.meta)) return false; for (let i = 0; i < this.length; i++) { - if (this[i] === other[i]) continue; - if (typeof this[i].equals !== 'function') return false; - if (!this[i].equals(other[i])) return false; + const a = this[i]; + const b = other[i]; + if (a === b) continue; + if (!a || typeof a.equals !== 'function') return false; + if (!a.equals(b)) return false; } return true; }; diff --git a/packages/core/test/test-skeleton.js b/packages/core/test/test-skeleton.js index cd3b22c..33d2e4a 100644 --- a/packages/core/test/test-skeleton.js +++ b/packages/core/test/test-skeleton.js @@ -45,6 +45,10 @@ function skeletonTrace(f) { return traceHolder.trace; } +function _analyzeAssertion(a) { + return Skeleton.analyzeAssertion(Immutable.fromJS(a)); +} + describe('skeleton', () => { const A = Struct.makeConstructor('A', ['x', 'y']); @@ -53,14 +57,14 @@ describe('skeleton', () => { describe('pattern analysis', () => { it('should handle leaf captures', () => { - expect(Immutable.fromJS(Skeleton.analyzeAssertion(A(B(_$), _$)))) + expect(Immutable.fromJS(_analyzeAssertion(A(B(_$), _$)))) .to.equal(Immutable.fromJS({skeleton: [A.meta, [B.meta, null], null], constPaths: Immutable.fromJS([]), constVals: Immutable.fromJS([]), capturePaths: Immutable.fromJS([[0, 0], [1]])})); }); it('should handle atomic constants', () => { - expect(Immutable.fromJS(Skeleton.analyzeAssertion(A(B("x"), _$)))) + expect(Immutable.fromJS(_analyzeAssertion(A(B("x"), _$)))) .to.equal(Immutable.fromJS({skeleton: [A.meta, [B.meta, null], null], constPaths: Immutable.fromJS([[0, 0]]), constVals: Immutable.fromJS(["x"]), @@ -74,7 +78,7 @@ describe('skeleton', () => { // that situation without the static analysis half of the code. // TODO later. let complexPlaceholder = new Object(); - expect(Immutable.fromJS(Skeleton.analyzeAssertion(A(complexPlaceholder, C(_$))))) + expect(Immutable.fromJS(_analyzeAssertion(A(complexPlaceholder, C(_$))))) .to.equal(Immutable.fromJS({skeleton: [A.meta, null, [C.meta, null]], constPaths: Immutable.fromJS([[0]]), constVals: Immutable.fromJS([complexPlaceholder]), @@ -87,21 +91,21 @@ describe('skeleton', () => { // will end up being complex at runtime. We can't properly test // that situation without the static analysis half of the code. // TODO later. - expect(Immutable.fromJS(Skeleton.analyzeAssertion(A(B(B("y")), _$("rhs", C(__)))))) + expect(Immutable.fromJS(_analyzeAssertion(A(B(B("y")), _$("rhs", C(__)))))) .to.equal(Immutable.fromJS({skeleton: [A.meta, [B.meta, [B.meta, null]], [C.meta, null]], constPaths: Immutable.fromJS([[0, 0, 0]]), constVals: Immutable.fromJS(["y"]), capturePaths: Immutable.fromJS([[1]])})); }); it('should handle list patterns with discards', () => { - expect(Immutable.fromJS(Skeleton.analyzeAssertion([__, __]))) + expect(Immutable.fromJS(_analyzeAssertion([__, __]))) .to.equal(Immutable.fromJS({skeleton: [2, null, null], constPaths: Immutable.fromJS([]), constVals: Immutable.fromJS([]), capturePaths: Immutable.fromJS([])})); }); it('should handle list patterns with constants and captures', () => { - expect(Immutable.fromJS(Skeleton.analyzeAssertion(["hi", _$, _$]))) + expect(Immutable.fromJS(_analyzeAssertion(["hi", _$, _$]))) .to.equal(Immutable.fromJS({skeleton: [3, null, null, null], constPaths: Immutable.fromJS([[0]]), constVals: Immutable.fromJS(["hi"]), @@ -111,8 +115,8 @@ describe('skeleton', () => { describe('nested structs', () => { let trace = skeletonTrace((i, traceHolder) => { - i.addHandler(Skeleton.analyzeAssertion(A(B(_$), _$)), eventCallback(traceHolder, "AB")); - i.addHandler(Skeleton.analyzeAssertion(A(B("x"), _$)), eventCallback(traceHolder, "ABx")); + i.addHandler(_analyzeAssertion(A(B(_$), _$)), eventCallback(traceHolder, "AB")); + i.addHandler(_analyzeAssertion(A(B("x"), _$)), eventCallback(traceHolder, "ABx")); let complexConstantPattern1 = {skeleton: [A.meta, null, [C.meta, null]], constPaths: Immutable.fromJS([[0]]), constVals: Immutable.fromJS([B("y")]), @@ -125,10 +129,10 @@ describe('skeleton', () => { capturePaths: Immutable.fromJS([[1]])}; i.addHandler(complexConstantPattern2, eventCallback(traceHolder, "ABByC")); - i.addAssertion(A(B("x"),C(1))); - i.addAssertion(A(B("y"),C(2))); - i.addAssertion(A(B(B("y")),C(2))); - i.addAssertion(A(B("z"),C(3))); + i.addAssertion(Immutable.fromJS(A(B("x"),C(1)))); + i.addAssertion(Immutable.fromJS(A(B("y"),C(2)))); + i.addAssertion(Immutable.fromJS(A(B(B("y")),C(2)))); + i.addAssertion(Immutable.fromJS(A(B("z"),C(3)))); }); // trace.forEach((e) => { console.log(e.toString()) }); @@ -146,12 +150,12 @@ describe('skeleton', () => { describe('simple detail-erasing trace', () => { let trace = skeletonTrace((i, traceHolder) => { - i.addHandler(Skeleton.analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT")); + i.addHandler(_analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT")); - i.addAssertion(["hi", 123]); - i.addAssertion(["hi", 234]); - i.removeAssertion(["hi", 123]); - i.removeAssertion(["hi", 234]); + i.addAssertion(Immutable.fromJS(["hi", 123])); + i.addAssertion(Immutable.fromJS(["hi", 234])); + i.removeAssertion(Immutable.fromJS(["hi", 123])); + i.removeAssertion(Immutable.fromJS(["hi", 234])); }); it('should have one add and one remove', () => { @@ -164,9 +168,9 @@ describe('skeleton', () => { describe('handler added after assertion (1)', () => { let trace = skeletonTrace((i, traceHolder) => { - i.addAssertion(["hi", 123, 234]); - i.addHandler(Skeleton.analyzeAssertion(["hi", _$, _$]), eventCallback(traceHolder, "X")); - i.removeAssertion(["hi", 123, 234]); + i.addAssertion(Immutable.fromJS(["hi", 123, 234])); + i.addHandler(_analyzeAssertion(["hi", _$, _$]), eventCallback(traceHolder, "X")); + i.removeAssertion(Immutable.fromJS(["hi", 123, 234])); }); it('should get two events', () => { @@ -178,9 +182,9 @@ describe('skeleton', () => { describe('handler added after assertion (2)', () => { let trace = skeletonTrace((i, traceHolder) => { - i.addAssertion(["hi", 123, 234]); - i.addHandler(Skeleton.analyzeAssertion(_$), eventCallback(traceHolder, "X")); - i.removeAssertion(["hi", 123, 234]); + i.addAssertion(Immutable.fromJS(["hi", 123, 234])); + i.addHandler(_analyzeAssertion(_$), eventCallback(traceHolder, "X")); + i.removeAssertion(Immutable.fromJS(["hi", 123, 234])); }); it('should get two events', () => { @@ -192,12 +196,12 @@ describe('skeleton', () => { describe('handler removed before assertion removed', () => { let trace = skeletonTrace((i, traceHolder) => { - i.addAssertion(["hi", 123, 234]); - let h = Skeleton.analyzeAssertion(["hi", _$, _$]); + i.addAssertion(Immutable.fromJS(["hi", 123, 234])); + let h = _analyzeAssertion(["hi", _$, _$]); h.callback = eventCallback(traceHolder, "X") i.addHandler(h, h.callback); i.removeHandler(h, h.callback); - i.removeAssertion(["hi", 123, 234]); + i.removeAssertion(Immutable.fromJS(["hi", 123, 234])); }); it('should get one event', () => { @@ -208,24 +212,24 @@ describe('skeleton', () => { describe('simple list assertions trace', () => { let trace = skeletonTrace((i, traceHolder) => { - i.addHandler(Skeleton.analyzeAssertion(["hi", _$, _$]), eventCallback(traceHolder, "3-EVENT")); - i.addHandler(Skeleton.analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT")); + i.addHandler(_analyzeAssertion(["hi", _$, _$]), eventCallback(traceHolder, "3-EVENT")); + i.addHandler(_analyzeAssertion([__, __]), eventCallback(traceHolder, "2-EVENT")); - i.addAssertion(["hi", 123, 234]); - i.addAssertion(["hi", 999, 999]); - i.addAssertion(["hi", 123]); - i.addAssertion(["hi", 123, 234]); - i.sendMessage(["hi", 303]); - i.sendMessage(["hi", 303, 404]); - i.sendMessage(["hi", 303, 404, 808]); - i.removeAssertion(["hi", 123, 234]); - i.removeAssertion(["hi", 999, 999]); - i.removeAssertion(["hi", 123, 234]); - i.addAssertion(["hi", 123]); - i.addAssertion(["hi", 234]); - i.removeAssertion(["hi", 123]); - i.removeAssertion(["hi", 123]); - i.removeAssertion(["hi", 234]); + i.addAssertion(Immutable.fromJS(["hi", 123, 234])); + i.addAssertion(Immutable.fromJS(["hi", 999, 999])); + i.addAssertion(Immutable.fromJS(["hi", 123])); + i.addAssertion(Immutable.fromJS(["hi", 123, 234])); + i.sendMessage(Immutable.fromJS(["hi", 303])); + i.sendMessage(Immutable.fromJS(["hi", 303, 404])); + i.sendMessage(Immutable.fromJS(["hi", 303, 404, 808])); + i.removeAssertion(Immutable.fromJS(["hi", 123, 234])); + i.removeAssertion(Immutable.fromJS(["hi", 999, 999])); + i.removeAssertion(Immutable.fromJS(["hi", 123, 234])); + i.addAssertion(Immutable.fromJS(["hi", 123])); + i.addAssertion(Immutable.fromJS(["hi", 234])); + i.removeAssertion(Immutable.fromJS(["hi", 123])); + i.removeAssertion(Immutable.fromJS(["hi", 123])); + i.removeAssertion(Immutable.fromJS(["hi", 234])); }); it('should have 8 entries', () => {