From 6c2584460cff6e6b0064883d38637ffbdba89a19 Mon Sep 17 00:00:00 2001 From: Bigjango13 Date: Thu, 1 Feb 2024 18:17:42 -0500 Subject: [PATCH] Merge branch 'master' of https://gitea.thebrokenrail.com/minecraft-pi-reborn/symbol-processor --- src/common.ts | 75 ++++++++++++++++++++++++++++++++++ src/loader.ts | 22 +++------- src/property.ts | 14 +++++-- src/struct.ts | 105 ++++++++++++++++++++++-------------------------- src/vtable.ts | 47 ++++++++++++---------- 5 files changed, 166 insertions(+), 97 deletions(-) diff --git a/src/common.ts b/src/common.ts index e996743..a79439a 100644 --- a/src/common.ts +++ b/src/common.ts @@ -82,4 +82,79 @@ export function assertSize(name: string, size: number, isDefined: boolean) { } // Return return out; +} +export function safeParseInt(str: string) { + const x = parseInt(str); + if (isNaN(x)) { + throw new Error('Invalid Integer: ' + str); + } + return x; +} +export function stripArrayData(propertyName: string) { + const index = propertyName.indexOf('['); + if (index !== -1) { + propertyName = propertyName.substring(0, index); + } + return propertyName; +} +export function getSizeMultiplierFromArrayData(propertyName: string) { + let multiplier = 1; + // Check If Array Data Is Present + const index = propertyName.indexOf('['); + if (index === -1) { + return multiplier; + } + propertyName = propertyName.substring(index + 1); + // Strip Last ] + if (!propertyName.endsWith(']')) { + syntaxError('Expecting ]'); + } else { + propertyName = propertyName.substring(0, propertyName.length - 1); + } + // Split + const parts = propertyName.split(']['); + // Calculate Multiplier + for (const part of parts) { + multiplier *= safeParseInt(part); + } + // Return + return multiplier; +} +export function getSelfArg(type: string) { + return `${type} *self`; +} +export class Size { + #size: number | null; + #isExact: boolean; + readonly #pointerAligned: boolean; + + constructor(pointerAligned: boolean) { + this.#size = null; + this.#isExact = false; + this.#pointerAligned = pointerAligned; + } + + get() { + return this.#size; + } + set(size: number, isExact: boolean) { + if (size < MIN_SIZE) { + throw new Error(`Size Too Small: ${toHex(size)}`); + } + if (this.#isExact) { + throw new Error('Cannot Change Size After Being Set By Base Structure'); + } + if (this.#size !== null && size < this.#size) { + throw new Error('Cannot Decrease VTable Size'); + } + this.#size = size; + this.#isExact = isExact; + if (this.#pointerAligned && (this.#size % POINTER_SIZE) !== 0) { + throw new Error(`Invalid Size: ${toHex(this.#size)}`); + } + } + // Whether This Is An Exact Limit Or Just A Lower Bound + isExact() { + return this.#isExact; + } } \ No newline at end of file diff --git a/src/loader.ts b/src/loader.ts index f0d6025..f865463 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -1,17 +1,10 @@ -import { COMMENT, EXTENSION, parseTypeAndName, readDefinition, syntaxError } from './common'; +import { COMMENT, EXTENSION, getSelfArg, parseTypeAndName, readDefinition, safeParseInt, syntaxError } from './common'; import { isCppAllowed } from './map'; import { Method } from './method'; import { SimpleProperty, StaticProperty } from './property'; import { Struct } from './struct'; // Error Handling -function safeParseInt(str: string) { - const x = parseInt(str); - if (isNaN(x)) { - throw new Error('Invalid Integer: ' + str); - } - return x; -} export class ErrorOnLine { readonly error: unknown; readonly file: string; @@ -51,7 +44,7 @@ function parseMethod(args: string, self: string, insertSelfArg: boolean) { syntaxError('Invalid Method Arguments'); } if (insertSelfArg) { - let selfArg = `(${self} *self`; + let selfArg = `(${getSelfArg(self)}`; if (methodArgs !== '()') { selfArg += ', '; } @@ -120,21 +113,18 @@ export function load(target: Struct, name: string, isExtended: boolean) { } case 'size': { // Set Size - if (!isExtended) { - target.setSize(safeParseInt(args)); - } + target.setSize(safeParseInt(args), !isExtended); break; } case 'vtable-size': { // Set VTable Size - if (!isExtended) { - target.setVTableSize(safeParseInt(args)); - } + target.setVTableSize(safeParseInt(args), !isExtended); break; } case 'vtable': { // Set VTable Address - if (!isExtended) { + target.ensureVTable(); + if (!isExtended && args.length > 0) { target.setVTableAddress(safeParseInt(args)); } break; diff --git a/src/property.ts b/src/property.ts index 28fa3da..303fead 100644 --- a/src/property.ts +++ b/src/property.ts @@ -1,4 +1,4 @@ -import { POINTER_SIZE } from './common'; +import { POINTER_SIZE, toHex, getSizeMultiplierFromArrayData } from './common'; import { getStructure } from './map'; export interface Property { @@ -13,12 +13,14 @@ export class SimpleProperty implements Property { readonly #offset: number; readonly #type: string; readonly #name: string; + readonly #arraySizeMultiplier: number; // Constructor constructor(offset: number, type: string, name: string) { this.#offset = offset; this.#type = type; this.#name = name; + this.#arraySizeMultiplier = getSizeMultiplierFromArrayData(this.#name); } // Getters @@ -33,7 +35,7 @@ export class SimpleProperty implements Property { } // Size - propertySize() { + #rawPropertySize() { if (this.#type.endsWith('*')) { // Pointer return POINTER_SIZE; @@ -64,6 +66,9 @@ export class SimpleProperty implements Property { return structure.getSize(true); } } + propertySize() { + return this.#arraySizeMultiplier * this.#rawPropertySize(); + } // Alignment propertyAlignment() { @@ -108,6 +113,9 @@ export class StaticProperty { // Constructor constructor(address: number, type: string, name: string, self: string, isArray: boolean) { + if (name.includes('[')) { + throw new Error('Use "static-property-array" For Arrays'); + } this.address = address; this.#type = type; this.#name = name; @@ -139,4 +147,4 @@ export class StaticProperty { generateMacro() { return `#define ${this.getName()} (${this.#isArray ? '' : '*'}${this.getName()}_pointer)\n`; } -} \ No newline at end of file +} diff --git a/src/struct.ts b/src/struct.ts index 0abf1df..b58db90 100644 --- a/src/struct.ts +++ b/src/struct.ts @@ -1,4 +1,4 @@ -import { INDENT, MIN_SIZE, formatType, STRUCTURE_FILES, toHex, getAssertFunction, assertSize } from './common'; +import { INDENT, MIN_SIZE, formatType, STRUCTURE_FILES, toHex, getAssertFunction, assertSize, stripArrayData, Size } from './common'; import { Method } from './method'; import { Property, StaticProperty } from './property'; import { VTable } from './vtable'; @@ -8,7 +8,7 @@ export class Struct { #vtable: VTable | null; readonly #methods: Method[]; readonly #properties: Property[]; - #size: number | null; + readonly #size: Size; readonly #dependencies: string[]; readonly #staticProperties: StaticProperty[]; @@ -18,7 +18,7 @@ export class Struct { this.#methods = []; this.#properties = []; this.#vtable = null; - this.#size = null; + this.#size = new Size(false); this.#dependencies = []; this.#staticProperties = []; } @@ -32,7 +32,7 @@ export class Struct { } // Ensure VTable Exists - #ensureVTable() { + ensureVTable() { if (this.#vtable === null) { this.#vtable = new VTable(this.#name); this.addProperty(this.#vtable); @@ -40,28 +40,36 @@ export class Struct { } // Setters - setSize(size: number) { - this.#size = size; + setSize(size: number, isExact: boolean) { + this.#size.set(size, isExact); } // Getters #roundSize(size: number) { const alignment = this.getAlignment(); return Math.ceil(size / alignment) * alignment; } - getSize(round: boolean) { - if (this.#isUndefined()) { - return 0; + #getRealSize() { + // Get Size Computed Purely From Properties + let size = MIN_SIZE; + for (const property of this.#properties) { + const newSize = property.propertyOffset() + property.propertySize(); + if (newSize > size) { + size = newSize; + } } + return size; + } + getSize(round: boolean) { let size; - if (this.#size !== null) { - size = this.#size; + if (this.#size.isExact()) { + // Exact Size Is Specified + size = this.#size.get()!; } else { - size = MIN_SIZE; - for (const property of this.#properties) { - const newSize = property.propertyOffset() + property.propertySize(); - if (newSize > size) { - size = newSize; - } + // Specified Size Is A Lower Bound + size = this.#getRealSize(); + const specifiedSize = this.#size.get(); + if (specifiedSize !== null && specifiedSize > size) { + size = specifiedSize; } } if (round) { @@ -89,7 +97,7 @@ export class Struct { throw new Error(); } if (isVirtual) { - this.#ensureVTable(); + this.ensureVTable(); this.#vtable!.add(method); } else { this.#methods.push(method); @@ -110,40 +118,33 @@ export class Struct { } // Configure VTable - setVTableSize(size: number) { - this.#ensureVTable(); - this.#vtable!.setSize(size); + setVTableSize(size: number, isExact: boolean) { + this.ensureVTable(); + this.#vtable!.setSize(size, isExact); } setVTableAddress(address: number) { - this.#ensureVTable(); + this.ensureVTable(); this.#vtable!.setAddress(address); } - // Check If Structure Is Undefined - #isUndefined() { - return this.#properties.length === 0 && this.#size === null; - } - // Check #check() { // Sort Properties this.#properties.sort((a, b) => a.propertyOffset() - b.propertyOffset()); // Check Size - const size = this.getSize(true); - if (this.#size !== null) { + if (this.#size.isExact()) { + const size = this.getSize(true); // Check Alignment - if (size !== this.#size) { + const specifiedSize = this.#size.get()!; + if (size !== specifiedSize) { throw new Error('Size Misaligned'); } // Check If Size Is Too Small - const lastProperty = this.#properties[this.#properties.length - 1]; - if (lastProperty) { - let realSize = lastProperty.propertyOffset() + lastProperty.propertySize(); - realSize = this.#roundSize(realSize); - if (realSize > this.#size) { - throw new Error(`Structure Size Too Small: ${this.#size}`); - } + let realSize = this.#getRealSize(); + realSize = this.#roundSize(realSize); + if (realSize > specifiedSize) { + throw new Error(`Structure Size Too Small: ${toHex(specifiedSize)}`); } } } @@ -153,26 +154,21 @@ export class Struct { // Null A = Start Of Structure // Null B = End Of Structure let neededPadding = 0; + const size = this.getSize(true); if (a === null) { // Start Of Structure Padding if (b !== null) { neededPadding = b.propertyOffset(); } else { // Both A And B Are Null - if (this.#size !== null) { - neededPadding = this.#size; - } else { - neededPadding = MIN_SIZE; - } + neededPadding = size; } } else if (b === null) { // End Of Structure Padding - if (this.#size !== null) { - const realSize = a.propertyOffset() + a.propertySize(); - const realRoundedSize = this.#roundSize(realSize); - if (realRoundedSize !== this.#size) { - neededPadding = this.#size - realSize; - } + const realSize = this.#getRealSize(); + const realRoundedSize = this.#roundSize(realSize); + if (realRoundedSize !== size) { + neededPadding = size - realSize; } } else { // Inner Structure Padding @@ -180,7 +176,7 @@ export class Struct { neededPadding = b.propertyOffset() - realSizeSoFar; } if (neededPadding < 0) { - throw new Error('Overlapping properties detected!'); + throw new Error('Overlapping Properties Detected!'); } return neededPadding; } @@ -204,11 +200,6 @@ export class Struct { out += `extern ${method.generateDefinition()}`; } - // Early Exit For Undefined Structures - if (this.#isUndefined()) { - return out; - } - // VTable if (this.#vtable !== null) { out += this.#vtable.generate(); @@ -245,14 +236,14 @@ export class Struct { const assertFunction = getAssertFunction(); for (let i = 0; i < this.#properties.length; i++) { const property = this.#properties[i]!; - const name = property.propertyName(); + const name = stripArrayData(property.propertyName()); const offset = property.propertyOffset(); out += `${assertFunction}(offsetof(${this.#name}, ${name}) == ${toHex(offset)}, "Invalid Offset");\n`; } // Sanity Check Size const size = this.getSize(true); - const isSizeDefined = this.#size !== null; + const isSizeDefined = this.#size.isExact(); out += assertSize(this.#name, size, isSizeDefined); // Allocation Function @@ -292,7 +283,7 @@ export class Struct { } // Allocation Function - if (this.#size !== null) { + if (this.#size.isExact()) { declarations += `${this.#name} *alloc_${this.#name}() {\n`; declarations += `${INDENT}return new ${this.#name};\n`; declarations += '}\n'; diff --git a/src/vtable.ts b/src/vtable.ts index 5a93f70..002b2a6 100644 --- a/src/vtable.ts +++ b/src/vtable.ts @@ -1,19 +1,24 @@ -import { INDENT, POINTER_SIZE, assertSize, toHex } from './common'; +import { INDENT, POINTER_SIZE, Size, assertSize, getSelfArg, toHex } from './common'; import { Method } from './method'; import { Property } from './property'; export class VTable implements Property { readonly #name: string; #address: number | null; - #size: number | null; + readonly #size: Size; readonly #methods: Method[]; // Constructor constructor(name: string) { this.#name = name; this.#address = null; - this.#size = null; + this.#size = new Size(true); this.#methods = []; + // Add Destructors (https://stackoverflow.com/a/17960941) + const destructor_return = `${name} *`; + const destructor_args = `(${getSelfArg(name)})`; + this.add(new Method(name, 'destructor_complete', destructor_return, destructor_args, 0x0)); + this.add(new Method(name, 'destructor_deleting', destructor_return, destructor_args, 0x4)); } // Property Information @@ -37,11 +42,8 @@ export class VTable implements Property { setAddress(address: number) { this.#address = address; } - setSize(size: number) { - this.#size = size; - if ((this.#size % POINTER_SIZE) !== 0) { - throw new Error(`Invalid VTable Size: ${this.#size}`); - } + setSize(size: number, isExact: boolean) { + this.#size.set(size, isExact); } // Add To VTable @@ -49,14 +51,13 @@ export class VTable implements Property { // Check Offset const offset = method.address; if ((offset % POINTER_SIZE) !== 0) { - throw new Error(`Invalid VTable Offset: ${offset}`); - } - // Check Size - if (this.#size !== null && (offset + POINTER_SIZE) > this.#size) { - throw new Error(`VTable Offset Too Large: ${offset}`); + throw new Error(`Invalid VTable Offset: ${toHex(offset)}`); } // Add const index = offset / POINTER_SIZE; + if (this.#methods[index]) { + throw new Error(`Duplicate Virtual Method At Offset: ${toHex(offset)}`); + } this.#methods[index] = method; } @@ -68,12 +69,15 @@ export class VTable implements Property { // Check #check() { // Check Size - if (this.#size !== null) { - const maxMethodCount = this.#size / POINTER_SIZE; - if (maxMethodCount < this.#methods.length) { - throw new Error(`VTable Size Too Small: ${this.#size}`); + const size = this.#size.get(); + if (size !== null) { + const maxMethodCount = size / POINTER_SIZE; + if (this.#size.isExact() && maxMethodCount < this.#methods.length) { + throw new Error(`VTable Size Too Small: ${toHex(size)}`); + } + if (maxMethodCount > this.#methods.length) { + this.#methods.length = maxMethodCount; } - this.#methods.length = maxMethodCount; } } @@ -108,8 +112,9 @@ export class VTable implements Property { out += `};\n`; // Sanity Check Size - const isSizeDefined = this.#size !== null; - const size = isSizeDefined ? this.#size! : (this.#methods.length * POINTER_SIZE); + const rawSize = this.#size.get(); + const isSizeDefined = this.#size.isExact(); + const size = isSizeDefined ? rawSize! : (this.#methods.length * POINTER_SIZE); out += assertSize(this.#getName(), size, isSizeDefined); // Pointers @@ -164,7 +169,7 @@ export class VTable implements Property { } // Duplication Method - if (this.#size !== null) { + if (this.#size.isExact()) { declarations += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable) {\n`; declarations += `${INDENT}${this.#getName()} *obj = new ${this.#getName()};\n`; declarations += `${INDENT}if (obj == NULL) {\n`;