Skip to content

Commit adf9c13

Browse files
committed
Add type inference / bounds checking for .slice()
1 parent 4e0ed3a commit adf9c13

File tree

8 files changed

+84
-12
lines changed

8 files changed

+84
-12
lines changed

packages/cashc/src/Errors.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,19 @@ export class ArrayElementError extends CashScriptError {
254254

255255
export class IndexOutOfBoundsError extends CashScriptError {
256256
constructor(
257-
node: TupleIndexOpNode | BinaryOpNode,
257+
node: TupleIndexOpNode | BinaryOpNode | SliceNode,
258258
) {
259259
if (node instanceof TupleIndexOpNode) {
260260
super(node, `Index ${node.index} out of bounds`);
261-
} else if (node.operator === BinaryOperator.SPLIT && node.right instanceof IntLiteralNode) {
261+
} else if (
262+
node instanceof BinaryOpNode && node.operator === BinaryOperator.SPLIT && node.right instanceof IntLiteralNode
263+
) {
262264
const splitIndex = Number(node.right.value);
263265
super(node, `Split index ${splitIndex} out of bounds for type ${node.left.type}`);
266+
} else if (node instanceof SliceNode) {
267+
const start = node.start instanceof IntLiteralNode ? Number(node.start.value) : 'start';
268+
const end = node.end instanceof IntLiteralNode ? Number(node.end.value) : 'end';
269+
super(node, `Slice indexes (${start}, ${end}) out of bounds for type ${node.element.type}`);
264270
} else {
265271
super(node, 'Index out of bounds');
266272
}

packages/cashc/src/semantic/TypeCheckTraversal.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ export default class TypeCheckTraversal extends AstTraversal {
166166
}
167167

168168
node.type = node.index === 0 ? (node.tuple.type as TupleType).leftType : (node.tuple.type as TupleType).rightType;
169-
170169
return node;
171170
}
172171

@@ -179,9 +178,7 @@ export default class TypeCheckTraversal extends AstTraversal {
179178
expectInt(node, node.start.type);
180179
expectInt(node, node.end.type);
181180

182-
// TODO: Proper typing
183-
node.type = node.element.type instanceof BytesType ? new BytesType() : PrimitiveType.STRING;
184-
181+
node.type = inferSliceType(node);
185182
return node;
186183
}
187184

@@ -385,6 +382,10 @@ function expectParameters(node: NodeWithParameters, actual: Type[], expected: Ty
385382

386383
// We only call this function for the split operator, so we assume that the node.op is SPLIT
387384
function inferTupleType(node: BinaryOpNode): Type {
385+
if (node.right instanceof IntLiteralNode && Number(node.right.value) < 0) {
386+
throw new IndexOutOfBoundsError(node);
387+
}
388+
388389
// string.split() -> string, string
389390
if (node.left.type === PrimitiveType.STRING) {
390391
return new TupleType(PrimitiveType.STRING, PrimitiveType.STRING);
@@ -406,10 +407,6 @@ function inferTupleType(node: BinaryOpNode): Type {
406407
return new TupleType(new BytesType(splitIndex), new BytesType());
407408
}
408409

409-
if (splitIndex < 0) {
410-
throw new IndexOutOfBoundsError(node);
411-
}
412-
413410
if (splitIndex > expressionType.bound) {
414411
throw new IndexOutOfBoundsError(node);
415412
}
@@ -420,3 +417,48 @@ function inferTupleType(node: BinaryOpNode): Type {
420417
new BytesType(expressionType.bound! - splitIndex),
421418
);
422419
}
420+
421+
function inferSliceType(node: SliceNode): Type {
422+
if (node.start instanceof IntLiteralNode && Number(node.start.value) < 0) {
423+
throw new IndexOutOfBoundsError(node);
424+
}
425+
426+
if (node.end instanceof IntLiteralNode && Number(node.end.value) < 0) {
427+
throw new IndexOutOfBoundsError(node);
428+
}
429+
430+
// string.slice() -> string
431+
if (node.element.type === PrimitiveType.STRING) {
432+
return PrimitiveType.STRING;
433+
}
434+
435+
// If the expression is not a bytes type, then it must be a different compatible type (e.g. sig/pubkey)
436+
const expressionType = node.element.type instanceof BytesType ? node.element.type : new BytesType();
437+
438+
if (expressionType.bound !== undefined) {
439+
if (node.start instanceof IntLiteralNode && Number(node.start.value) >= expressionType.bound) {
440+
throw new IndexOutOfBoundsError(node);
441+
}
442+
443+
if (node.end instanceof IntLiteralNode && Number(node.end.value) > expressionType.bound) {
444+
throw new IndexOutOfBoundsError(node);
445+
}
446+
}
447+
448+
// bytes.slice(variable, variable) -> bytes
449+
// bytes.slice(NumberLiteral, variable) -> bytes
450+
// bytes.slice(variable, NumberLiteral) -> bytes
451+
if (!(node.start instanceof IntLiteralNode) || !(node.end instanceof IntLiteralNode)) {
452+
return new BytesType();
453+
}
454+
455+
const start = Number(node.start.value);
456+
const end = Number(node.end.value);
457+
458+
if (start > end) {
459+
throw new IndexOutOfBoundsError(node);
460+
}
461+
462+
// bytes.slice(NumberLiteral start, NumberLiteral end) -> bytes(end - start)
463+
return new BytesType(end - start);
464+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
contract Test(bytes8 b) {
2+
function spend() {
3+
bytes2 x = b.slice(2, 6);
4+
require(x != b);
5+
}
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
contract Test(bytes8 b) {
2+
function spend() {
3+
bytes x = b.slice(6, 12);
4+
require(x != b);
5+
}
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
contract Test(bytes8 b) {
2+
function spend() {
3+
bytes x = b.slice(4, -4);
4+
require(x != b);
5+
}
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
contract Test(bytes8 b) {
2+
function spend() {
3+
bytes x = b.slice(6, 2);
4+
require(x != b);
5+
}
6+
}

packages/cashc/test/generation/fixtures.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,7 @@ export const fixtures: Fixture[] = [
887887
bytecode: '14 OP_SPLIT OP_DROP OP_0 14 OP_NUM2BIN OP_EQUAL',
888888
debug: {
889889
bytecode: '01147f750001148087',
890-
sourceMap: '3:34:3:36;:20::37:1;;4:31:4:32:0;:23::33:1;;:8::35',
890+
sourceMap: '3:36:3:38;:22::39:1;;4:31:4:32:0;:23::33:1;;:8::35',
891891
logs: [],
892892
requires: [
893893
{
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
contract Slice(bytes32 data) {
22
function spend() {
3-
bytes pkh = data.slice(0, 20);
3+
bytes20 pkh = data.slice(0, 20);
44
require(pkh == bytes20(0));
55
}
66
}

0 commit comments

Comments
 (0)