Skip to content

Commit 8ab4e2c

Browse files
committed
fix(json-crdt-patch): 🐛 prevent recursion through new_val operation
1 parent d650489 commit 8ab4e2c

File tree

17 files changed

+75
-106
lines changed

17 files changed

+75
-106
lines changed

src/json-crdt-patch/Patch.ts

Lines changed: 31 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,4 @@
1-
import {
2-
NewConOp,
3-
NewObjOp,
4-
NewValOp,
5-
NewVecOp,
6-
NewStrOp,
7-
NewBinOp,
8-
NewArrOp,
9-
InsValOp,
10-
InsObjOp,
11-
InsVecOp,
12-
InsStrOp,
13-
InsBinOp,
14-
InsArrOp,
15-
DelOp,
16-
NopOp,
17-
} from './operations';
1+
import * as operations from './operations';
182
import {ITimestampStruct, ts, toDisplayString} from './clock';
193
import {SESSION} from './constants';
204
import {encode, decode} from './codec/binary';
@@ -24,21 +8,21 @@ import type {Printable} from '../util/print/types';
248
* A union type of all possible JSON CRDT patch operations.
259
*/
2610
export type JsonCrdtPatchOperation =
27-
| NewConOp
28-
| NewValOp
29-
| NewVecOp
30-
| NewObjOp
31-
| NewStrOp
32-
| NewBinOp
33-
| NewArrOp
34-
| InsValOp
35-
| InsObjOp
36-
| InsVecOp
37-
| InsStrOp
38-
| InsBinOp
39-
| InsArrOp
40-
| DelOp
41-
| NopOp;
11+
| operations.NewConOp
12+
| operations.NewValOp
13+
| operations.NewVecOp
14+
| operations.NewObjOp
15+
| operations.NewStrOp
16+
| operations.NewBinOp
17+
| operations.NewArrOp
18+
| operations.InsValOp
19+
| operations.InsObjOp
20+
| operations.InsVecOp
21+
| operations.InsStrOp
22+
| operations.InsBinOp
23+
| operations.InsArrOp
24+
| operations.DelOp
25+
| operations.NopOp;
4226

4327
/**
4428
* Represents a JSON CRDT patch.
@@ -135,27 +119,27 @@ export class Patch implements Printable {
135119
const patchOps = patch.ops;
136120
for (let i = 0; i < length; i++) {
137121
const op = ops[i];
138-
if (op instanceof DelOp) patchOps.push(new DelOp(ts(op.id), ts(op.obj), op.what));
139-
else if (op instanceof NewConOp) patchOps.push(new NewConOp(ts(op.id), op.val));
140-
else if (op instanceof NewVecOp) patchOps.push(new NewVecOp(ts(op.id)));
141-
else if (op instanceof NewValOp) patchOps.push(new NewValOp(ts(op.id), ts(op.val)));
142-
else if (op instanceof NewObjOp) patchOps.push(new NewObjOp(ts(op.id)));
143-
else if (op instanceof NewStrOp) patchOps.push(new NewStrOp(ts(op.id)));
144-
else if (op instanceof NewBinOp) patchOps.push(new NewBinOp(ts(op.id)));
145-
else if (op instanceof NewArrOp) patchOps.push(new NewArrOp(ts(op.id)));
146-
else if (op instanceof InsArrOp) patchOps.push(new InsArrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data.map(ts)));
147-
else if (op instanceof InsStrOp) patchOps.push(new InsStrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data));
148-
else if (op instanceof InsBinOp) patchOps.push(new InsBinOp(ts(op.id), ts(op.obj), ts(op.ref), op.data));
149-
else if (op instanceof InsValOp) patchOps.push(new InsValOp(ts(op.id), ts(op.obj), ts(op.val)));
150-
else if (op instanceof InsObjOp)
122+
if (op instanceof operations.DelOp) patchOps.push(new operations.DelOp(ts(op.id), ts(op.obj), op.what));
123+
else if (op instanceof operations.NewConOp) patchOps.push(new operations.NewConOp(ts(op.id), op.val));
124+
else if (op instanceof operations.NewVecOp) patchOps.push(new operations.NewVecOp(ts(op.id)));
125+
else if (op instanceof operations.NewValOp) patchOps.push(new operations.NewValOp(ts(op.id)));
126+
else if (op instanceof operations.NewObjOp) patchOps.push(new operations.NewObjOp(ts(op.id)));
127+
else if (op instanceof operations.NewStrOp) patchOps.push(new operations.NewStrOp(ts(op.id)));
128+
else if (op instanceof operations.NewBinOp) patchOps.push(new operations.NewBinOp(ts(op.id)));
129+
else if (op instanceof operations.NewArrOp) patchOps.push(new operations.NewArrOp(ts(op.id)));
130+
else if (op instanceof operations.InsArrOp) patchOps.push(new operations.InsArrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data.map(ts)));
131+
else if (op instanceof operations.InsStrOp) patchOps.push(new operations.InsStrOp(ts(op.id), ts(op.obj), ts(op.ref), op.data));
132+
else if (op instanceof operations.InsBinOp) patchOps.push(new operations.InsBinOp(ts(op.id), ts(op.obj), ts(op.ref), op.data));
133+
else if (op instanceof operations.InsValOp) patchOps.push(new operations.InsValOp(ts(op.id), ts(op.obj), ts(op.val)));
134+
else if (op instanceof operations.InsObjOp)
151135
patchOps.push(
152-
new InsObjOp(
136+
new operations.InsObjOp(
153137
ts(op.id),
154138
ts(op.obj),
155139
op.data.map(([key, value]) => [key, ts(value)]),
156140
),
157141
);
158-
else if (op instanceof NopOp) patchOps.push(new NopOp(ts(op.id), op.len));
142+
else if (op instanceof operations.NopOp) patchOps.push(new operations.NopOp(ts(op.id), op.len));
159143
}
160144
return patch;
161145
}

src/json-crdt-patch/PatchBuilder.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,12 @@ export class PatchBuilder {
154154
*
155155
* @param val Reference to another object.
156156
* @returns ID of the new operation.
157+
* @todo Rename to `newVal`.
157158
*/
158-
public val(val: ITimestampStruct): ITimestampStruct {
159+
public val(): ITimestampStruct {
159160
this.pad();
160161
const id = this.clock.tick(1);
161-
this.patch.ops.push(new NewValOp(id, val));
162+
this.patch.ops.push(new NewValOp(id));
162163
return id;
163164
}
164165

@@ -351,7 +352,9 @@ export class PatchBuilder {
351352
*/
352353
public jsonVal(value: unknown): ITimestampStruct {
353354
const id = this.const(value);
354-
return this.val(id);
355+
const valId = this.val();
356+
this.setVal(valId, id);
357+
return valId;
355358
}
356359

357360
/**

src/json-crdt-patch/builder/schema.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ export namespace nodes {
3232

3333
constructor(public readonly value: T) {
3434
super((builder) => {
35+
const valId = builder.val();
3536
const valueId = value.build(builder);
36-
return builder.val(valueId);
37+
builder.setVal(valId, valueId);
38+
return valId;
3739
});
3840
}
3941
}

src/json-crdt-patch/codec/__tests__/PatchFuzzer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class PatchFuzzer extends Fuzzer {
2828
() => builder.arr(),
2929
() => builder.str(),
3030
() => builder.bin(),
31-
() => builder.val(ts()),
31+
() => builder.val(),
3232
() => builder.const(RandomJson.generate()),
3333
() => builder.root(ts()),
3434
() =>

src/json-crdt-patch/codec/binary/Decoder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class Decoder extends CborDecoder<CrdtReader> {
7373
break;
7474
}
7575
case JsonCrdtPatchOpcode.new_val: {
76-
builder.val(this.decodeId());
76+
builder.val();
7777
break;
7878
}
7979
case JsonCrdtPatchOpcode.new_obj: {

src/json-crdt-patch/codec/binary/Encoder.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,7 @@ export class Encoder extends CborEncoder<CrdtWriter> {
9494
break;
9595
}
9696
case operations.NewValOp: {
97-
const operation = <operations.NewValOp>op;
98-
const val = operation.val;
9997
writer.u8(JsonCrdtPatchOpcode.new_val);
100-
this.encodeId(val);
10198
break;
10299
}
103100
case operations.NewObjOp: {

src/json-crdt-patch/codec/compact/decode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const decode = (data: types.CompactCodecPatch): Patch => {
3838
break;
3939
}
4040
case JsonCrdtPatchOpcode.new_val: {
41-
builder.val(timestamp(sid, op[1]));
41+
builder.val();
4242
break;
4343
}
4444
case JsonCrdtPatchOpcode.new_obj: {

src/json-crdt-patch/codec/compact/encode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export const encode = (patch: Patch): types.CompactCodecPatch => {
4747
res.push([JsonCrdtPatchOpcode.new_con, val]);
4848
}
4949
} else if (op instanceof operations.NewValOp) {
50-
res.push([JsonCrdtPatchOpcode.new_val, timestamp(sid, op.val)]);
50+
res.push([JsonCrdtPatchOpcode.new_val]);
5151
} else if (op instanceof operations.NewObjOp) {
5252
res.push([JsonCrdtPatchOpcode.new_obj]);
5353
} else if (op instanceof operations.NewVecOp) {

src/json-crdt-patch/codec/verbose/decode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const decode = (data: types.JsonCodecPatch): Patch => {
3030
break;
3131
}
3232
case 'new_val': {
33-
builder.val(decodeId(op.value));
33+
builder.val();
3434
break;
3535
}
3636
case 'new_obj': {

src/json-crdt-patch/codec/verbose/encode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export const encode = (patch: Patch): types.JsonCodecPatch => {
4545
} else if (op instanceof operations.NewBinOp) {
4646
ops.push({op: 'new_bin'});
4747
} else if (op instanceof operations.NewValOp) {
48-
ops.push({op: 'new_val', value: encodeTimestamp(op.val)});
48+
ops.push({op: 'new_val'});
4949
} else if (op instanceof operations.InsObjOp) {
5050
ops.push({
5151
op: 'ins_obj',

0 commit comments

Comments
 (0)