symbol-processor/src/vtable.ts

181 lines
5.5 KiB
TypeScript

import { INDENT, POINTER_SIZE, assertSize, 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 #methods: Method[];
// Constructor
constructor(name: string) {
this.#name = name;
this.#address = null;
this.#size = null;
this.#methods = [];
}
// Property Information
propertyOffset() {
return 0;
}
propertySize() {
return POINTER_SIZE;
}
propertyType() {
return this.#getName() + ' *';
}
propertyName() {
return 'vtable';
}
propertyAlignment() {
return 4;
}
// Setters
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}`);
}
}
// Add To VTable
add(method: Method) {
// 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}`);
}
// Add
const index = offset / POINTER_SIZE;
this.#methods[index] = method;
}
// Get Structure Name
#getName() {
return this.#name + '_vtable';
}
// 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}`);
}
this.#methods.length = maxMethodCount;
}
}
// Generate Code
generate() {
let out = '';
// Check
this.#check();
// Method Prototypes
for (let i = 0; i < this.#methods.length; i++) {
const info = this.#methods[i];
if (info) {
out += info.generateTypedef();
}
}
// Structure
out += `typedef struct ${this.#getName()} ${this.#getName()};\n`;
out += `struct ${this.#getName()} {\n`;
for (let i = 0; i < this.#methods.length; i++) {
let name = `unknown${i}`;
let type = 'void *';
const info = this.#methods[i];
if (info) {
name = info.shortName;
type = info.getType() + ' ';
}
out += `${INDENT}${type}${name};\n`;
}
out += `};\n`;
// Sanity Check Size
const isSizeDefined = this.#size !== null;
const size = isSizeDefined ? this.#size! : (this.#methods.length * POINTER_SIZE);
out += assertSize(this.#getName(), size, isSizeDefined);
// Pointers
if (this.#address !== null) {
// Base
out += `extern ${this.#getName()} *${this.#getName()}_base;\n`;
// Methods
for (let i = 0; i < this.#methods.length; i++) {
const info = this.#methods[i];
if (info) {
const type = `${info.getType()} *`;
out += `extern ${type}${info.getName()}_vtable_addr;\n`;
out += `extern ${info.generateDefinition('_non_virtual')}`;
}
}
}
// Duplication Method
if (isSizeDefined) {
out += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable);\n`;
}
// Return
return out;
}
// Generate Compiled Code
generateCode() {
let declarations = '';
let init = '';
// Check
this.#check();
// Pointers
if (this.#address !== null) {
// Base
init += `${INDENT}${this.#getName()}_base = (${this.#getName()} *) ${toHex(this.#address)};\n`;
declarations += `${this.#getName()} *${this.#getName()}_base;\n`;
// Methods
for (let i = 0; i < this.#methods.length; i++) {
const info = this.#methods[i];
if (info) {
const vtableAddress = this.#address + (i * POINTER_SIZE);
const type = `${info.getType()} *`;
init += `${INDENT}${info.getName()}_vtable_addr = (${type}) ${toHex(vtableAddress)};\n`;
declarations += `${type}${info.getName()}_vtable_addr;\n`;
init += `${INDENT}${info.getName()}_non_virtual = *${info.getName()}_vtable_addr;\n`;
declarations += info.generateDefinition();
}
}
}
// Duplication Method
if (this.#size !== null) {
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`;
declarations += `${INDENT}${INDENT}return NULL;\n`;
declarations += `${INDENT}}\n`;
declarations += `${INDENT}memcpy((void *) obj, (void *) vtable, sizeof (${this.#getName()});\n`;
declarations += `${INDENT}return obj;\n`;
declarations += '}\n';
}
// Return
return {declarations, init};
}
}