snyk-docker-plugin
Version:
Snyk CLI docker plugin
209 lines (192 loc) • 6.8 kB
text/typescript
// lineTable maps to the Go type https://pkg.go.dev/debug/gosym#LineTable
export class LineTable {
private static go12magic = 0xfffffffb;
private static go116magic = 0xfffffffa;
private static go118magic = 0xfffffff0;
private static go120magic = 0xfffffff1;
// the Go type contains some exported fields, but as we don't require anything, we don't export it.
// We only store what we require, so some fields are missing when compared to the Go implementation.
private version: pclnVersion = pclnVersion.unknown;
private filetab: Buffer;
private nfiletab: number;
private ptrsize: number;
private binary: ByteOrder;
private funcdata: Buffer;
private fileMap: Map<string, number> = new Map();
// https://pkg.go.dev/debug/gosym#NewLineTable, but only what we need to read out the files.
constructor(b: Buffer) {
// Check header: 4-byte magic, two zeros, pc quantum, pointer size.
if (
b.length < 16 ||
b[4] !== 0 ||
b[5] !== 0 ||
// pc quantum
(b[6] !== 1 && b[6] !== 2 && b[6] !== 4) ||
// pointer size
(b[7] !== 4 && b[7] !== 8)
) {
throw new Error("unknown header format");
}
// determine the endianness and the PCLN Table version
const leMagic = b.readUInt32LE(0);
const beMagic = b.readUInt32BE(0);
if (leMagic === LineTable.go12magic) {
this.binary = littleEndian;
this.version = pclnVersion.v12;
} else if (beMagic === LineTable.go12magic) {
this.binary = bigEndian;
this.version = pclnVersion.v12;
} else if (leMagic === LineTable.go116magic) {
this.binary = littleEndian;
this.version = pclnVersion.v116;
} else if (beMagic === LineTable.go116magic) {
this.binary = bigEndian;
this.version = pclnVersion.v116;
} else if (leMagic === LineTable.go118magic) {
this.binary = littleEndian;
this.version = pclnVersion.v118;
} else if (beMagic === LineTable.go118magic) {
this.binary = bigEndian;
this.version = pclnVersion.v118;
} else if (beMagic === LineTable.go120magic) {
this.binary = bigEndian;
this.version = pclnVersion.v120;
} else if (leMagic === LineTable.go120magic) {
this.binary = littleEndian;
this.version = pclnVersion.v118;
} else {
throw new Error(`unknown / unsupported Go version`);
}
this.ptrsize = b[7];
const uintptr = (b: Buffer): bigint => {
if (this.ptrsize === 4) {
return this.binary.Uint32(b);
}
return this.binary.Uint64(b);
};
const offset = (word: number): bigint => {
return uintptr(b.slice(8 + word * this.ptrsize));
};
const data = (word: number): Buffer => {
return b.slice(Number(offset(word))); // TODO: int conversion?
};
switch (this.version) {
case pclnVersion.v118:
case pclnVersion.v120:
this.nfiletab = Number(offset(1));
this.filetab = data(5);
this.funcdata = data(7);
break;
case pclnVersion.v116:
this.nfiletab = Number(offset(1));
this.filetab = data(4);
this.funcdata = data(6);
break;
case pclnVersion.v12:
const nfunctab = Number(this.uintptr(b.slice(8)));
this.funcdata = b;
const functab = b.slice(8 + this.ptrsize);
const functabsize = (nfunctab * 2 + 1) * this.functabFieldSize();
const fileoff = this.binary.Uint32(functab.slice(functabsize));
this.filetab = b.slice(Number(fileoff));
this.nfiletab = Number(this.binary.Uint32(this.filetab));
break;
default:
throw new Error("unreachable");
}
}
// go12MapFiles returns a list of files that have been found in the symbol table.
// In the original Go implementation, this function takes a map and object, but
// we don't need to construct a map and don't need the object.
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.5:src/debug/gosym/pclntab.go;l=669
public go12MapFiles(): string[] {
this.initFileMap();
const files: string[] = [];
for (const file of Object.keys(this.fileMap)) {
files.push(file);
}
return files;
}
// uintptr returns the pointer-sized value encoded at b.
// The pointer size is dictated by the table being read.
private uintptr(b: Buffer): bigint {
if (this.ptrsize === 4) {
return BigInt(this.binary.Uint32(b));
}
return this.binary.Uint64(b);
}
// functabFieldSize returns the sivze in bytes of a single functab field.
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.5:src/debug/gosym/pclntab.go;l=377
private functabFieldSize(): number {
if (this.version >= pclnVersion.v118) {
return 4;
}
return this.ptrsize;
}
// string returns a Go string found at offset.
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.5:src/debug/gosym/pclntab.go;l=372
private string(offset: number): string {
return this.stringFrom(this.funcdata, offset);
}
// initFileMap initializes the map from file name to file number.
// We technically don't need the number, but match Go's implementation 1:1 instead.
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.5:src/debug/gosym/pclntab.go;l=641
private initFileMap(): void {
if (this.fileMap.size > 0) {
return;
}
const files: Map<string, number> = new Map();
if (this.version === pclnVersion.v12) {
for (let i = 1; i < this.nfiletab; i++) {
const fileName = this.string(
Number(this.binary.Uint32(this.filetab.slice(4 * i))),
);
files[fileName] = i;
}
} else {
let pos: number = 0;
for (let i = 0; i < this.nfiletab; i++) {
const fileName = this.stringFrom(this.filetab, pos);
files[fileName] = pos;
pos += fileName.length + 1;
}
}
this.fileMap = files;
}
// stringFrom returns a Go string found at offset from a position.
// https://cs.opensource.google/go/go/+/refs/tags/go1.18.5:src/debug/gosym/pclntab.go;l=361
private stringFrom(arr: Buffer, offset: number): string {
const i = arr.slice(offset).indexOf(0);
const s = arr.slice(offset, offset + i).toString("ascii");
return s;
}
}
enum pclnVersion {
unknown,
v11,
v12,
v116,
v118,
v120,
}
// this and the endianness below matches the http://godoc.org/binary package in Go.
interface ByteOrder {
Uint32(b: Buffer): bigint;
Uint64(b: Buffer): bigint;
}
const bigEndian = {
Uint32(b: Buffer): bigint {
return BigInt(b.readUInt32BE(0));
},
Uint64(b: Buffer): bigint {
return b.readBigUInt64BE(0);
},
};
const littleEndian = {
Uint32(b: Buffer): bigint {
return BigInt(b.readUInt32LE(0));
},
Uint64(b: Buffer): bigint {
return b.readBigUInt64LE(0);
},
};