From c8243a4c2b4229d05a3089c5bcac75dffd301c75 Mon Sep 17 00:00:00 2001 From: Vizonex Date: Tue, 26 May 2026 13:49:30 -0500 Subject: [PATCH 1/4] implement bit shift node --- src/node/shift.ts | 29 +++++++++++++++++ src/shift-checker.ts | 77 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/node/shift.ts create mode 100644 src/shift-checker.ts diff --git a/src/node/shift.ts b/src/node/shift.ts new file mode 100644 index 0000000..8446fc2 --- /dev/null +++ b/src/node/shift.ts @@ -0,0 +1,29 @@ +import { Node } from './base'; + +/** + * This node consumes and stores an integer into a specified + * `field` from the input before forwarding execution. + */ +export class Shift extends Node { + /** + * @param field State's property name + * @param bits number of bits to shift it can be between 1 and 8 + * @param lshift if true, bits are shifted to the left. + * otherwise, it goes in the opposite direction. + */ + constructor (public readonly field: string, public readonly bits:number, public readonly lshift: boolean){ + const name = (lshift) ? "lshift" : "rshift"; + const operation = (bits > 1) ? `_${bits * 8}`: ""; + super(`${name}_${field}${operation}`); + if (/^_/.test(field)) { + throw new Error(`Can't use internal field in \`.${name}()\`: "${field}"`); + } + if (bits < 1 || bits > 8){ + throw new Error(`bits must be a number between 1 to 8`); + } + } + /** `.otherwise()` is not supported on this type of node as it consumes a bit. + * enabling this defeats the purpose. + */ + public otherwise(): this { throw new Error('Not supported'); } +} \ No newline at end of file diff --git a/src/shift-checker.ts b/src/shift-checker.ts new file mode 100644 index 0000000..8c4b020 --- /dev/null +++ b/src/shift-checker.ts @@ -0,0 +1,77 @@ +import { debuglog } from 'node:util'; + +import { Property } from './property'; +import { Node, Shift } from './node'; +import { Reachability } from './reachability'; + + +const debug = debuglog('llparse-builder:shift-checker'); + +/** + * Validates shift nodes to find invalid or missing properties or bit sizes + */ +export class ShiftChecker { + public readonly property_table = new Map(); + constructor (properties: readonly Property[]){ + properties.forEach((prop) => {this.property_table.set(prop.name, prop)}); + } + + /** + * validates a single shift node to see if a property exists + * to be fed bits to. It also has the ability of checking if numbers + * may overflow. + * + * @param shift the shift node to check. + */ + private validate_shift(shift: Shift): void{ + const property = this.property_table.get(shift.field); + if (!property){ + throw new Error(`"${shift.field}" has not been defined for ${shift.name}`); + } + let size; + switch (property.ty){ + case 'ptr': + throw new Error( + `${shift.field} cannot be provided to "${shift.name}" because field was defined as a "ptr"` + ); + case 'i8': + size = 1; + break; + case 'i16': + size = 2; + break; + case 'i32': + size = 4; + break; + case 'i64': + size = 8; + break; + default: + /* TODO (Vizonex): ensure this is unreachable. */ + /* UNREACHABLE */ + size = 0; + } + // Ensure bits can't overflow... + if (shift.bits > size){ + throw new Error(`${shift.bits} > ${size} and will cause node "${shift.field}" to overflow.`) + } + } + + /** + * Run shift checker pass on a graph starting from `root`. + * + * @param root Graph root node + */ + public check(root: Node): void { + const r = new Reachability(); + const nodes = r.build(root); + + for (const node of nodes){ + if (node instanceof Shift){ + debug('checking %j', node.name); + this.validate_shift(node); + } + } + } +} + From d8dacfb9964c02e71f0f5f7a5d088942aa8cfc82 Mon Sep 17 00:00:00 2001 From: Vizonex Date: Tue, 26 May 2026 14:14:33 -0500 Subject: [PATCH 2/4] implement lshift and rshift functionality. --- src/builder.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/builder.ts b/src/builder.ts index 6b308f2..89a14ec 100644 --- a/src/builder.ts +++ b/src/builder.ts @@ -7,6 +7,7 @@ import * as transform from './transform'; export { code, node, transform, Property, PropertyType, Span }; export { Edge } from './edge'; export { LoopChecker } from './loop-checker'; +export { ShiftChecker } from './shift-checker'; export { ISpanAllocatorResult, SpanAllocator } from './span-allocator'; export { Reachability } from './reachability'; @@ -112,6 +113,34 @@ export class Builder { return new node.Pause(errorCode, reason); } + // Shift + + /** + * Create a node that packs bits into a field in + * little endian format. + * + * state[field] <<= value; + * + * This node does not provide otherwise as this node must + * consume bytes upon execution. + */ + public lshift(field: string, bits: number): node.Shift { + return new node.Shift(field, bits, true); + } + + /** + * Create a node that packs bits into a field in + * big endian format. + * + * state[field] >>= value; + * + * This node does not provide otherwise as this node must + * consume bytes upon execution. + */ + public rshift(field: string, bits: number): node.Shift { + return new node.Shift(field, bits, false); + } + // Span /** From 7cc74ffa1130247f9bef81c0506f7ea963162ed4 Mon Sep 17 00:00:00 2001 From: Vizonex Date: Thu, 11 Jun 2026 21:25:26 -0500 Subject: [PATCH 3/4] Add shift-checker test --- test/shift-checker.test.ts | 81 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 test/shift-checker.test.ts diff --git a/test/shift-checker.test.ts b/test/shift-checker.test.ts new file mode 100644 index 0000000..040083b --- /dev/null +++ b/test/shift-checker.test.ts @@ -0,0 +1,81 @@ +import { beforeEach, describe, it, TestContext } from 'node:test'; +import { Builder, ShiftChecker } from '../src/builder'; + +describe ('LLParse/ShiftChecker', () => { + let b: Builder; + let sc: ShiftChecker; + + beforeEach(() => { + b = new Builder(); + }); + + it('should prohibit undefined properties', (t: TestContext) => { + const lshift = b.lshift("undefined", 1); + const start = b.node('start'); + + start + .otherwise(lshift); + lshift.skipTo(start); + sc = new ShiftChecker(b.properties); + + t.assert.throws(() => { + sc.check(start); + }, /has not been defined for.*undefined/) + }); + + it('should detect overflowing bits', (t: TestContext) => { + const rshift = b.rshift("defined", 8); + const start = b.node('start'); + + b.property('i8', "defined"); + + start + .otherwise(rshift); + rshift.skipTo(start); + + + sc = new ShiftChecker(b.properties); + t.assert.throws(() => { + sc.check(start); + }, /and will cause node .* to overflow./); + + }); + + it('should detect inapproperate types', (t: TestContext) => { + const rshift = b.rshift("defined", 8); + const start = b.node('start'); + + b.property('ptr', "defined"); + + start + .otherwise(rshift); + rshift.skipTo(start); + + + sc = new ShiftChecker(b.properties); + t.assert.throws(() => { + sc.check(start); + }, /defined cannot be provided to ".*" because field was defined as a "ptr"/); + }); + + it('should allow types that are smaller than itself', (t: TestContext) => { + const rshift = b.rshift("defined", 2); + const start = b.node('start'); + + /* hypothetically let's say we have an uint32_t in C but we only + need to pack a i16 bit integer, this is valid use-case because we can safely + back it even if the property is bigger than itself. */ + b.property('i32', "defined"); + + start + .otherwise(rshift); + rshift.skipTo(start); + + + sc = new ShiftChecker(b.properties); + t.assert.doesNotThrow(() => sc.check(start)); + }); + + + +}) \ No newline at end of file From a7c6088cdd0c92c42ebe4c2353a64be591ca5035 Mon Sep 17 00:00:00 2001 From: Vizonex Date: Thu, 11 Jun 2026 21:32:18 -0500 Subject: [PATCH 4/4] ensure typo in comments are fixed --- test/shift-checker.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/shift-checker.test.ts b/test/shift-checker.test.ts index 040083b..40a0841 100644 --- a/test/shift-checker.test.ts +++ b/test/shift-checker.test.ts @@ -64,9 +64,9 @@ describe ('LLParse/ShiftChecker', () => { /* hypothetically let's say we have an uint32_t in C but we only need to pack a i16 bit integer, this is valid use-case because we can safely - back it even if the property is bigger than itself. */ + pack it even if the property is bigger than itself. */ b.property('i32', "defined"); - + start .otherwise(rshift); rshift.skipTo(start);