This commit is contained in:
Bigjango13 2024-02-01 18:17:42 -05:00
parent ea31be1cb2
commit 6c2584460c
5 changed files with 166 additions and 97 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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`;
}
}
}

View File

@ -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';

View File

@ -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`;