Skip to content

Commit

Permalink
Dev Tools: Source map decoder (#590)
Browse files Browse the repository at this point in the history
* adding source map decoder
  • Loading branch information
barnjamin authored Jul 15, 2022
1 parent 7b2cfe1 commit 4266235
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 17 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
unit:
node_modules/.bin/cucumber-js --tags "@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.tealsign or @unit.dryrun or @unit.applications or @unit.responses or @unit.transactions or @unit.transactions.keyreg or @unit.transactions.payment or @unit.responses.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.responses.unlimited_assets or @unit.indexer.ledger_refactoring or @unit.algod.ledger_refactoring or @unit.dryrun.trace.application" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js
node_modules/.bin/cucumber-js --tags "@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.tealsign or @unit.dryrun or @unit.applications or @unit.responses or @unit.transactions or @unit.transactions.keyreg or @unit.transactions.payment or @unit.responses.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.responses.unlimited_assets or @unit.indexer.ledger_refactoring or @unit.algod.ledger_refactoring or @unit.dryrun.trace.application or @unit.sourcemap" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js

integration:
node_modules/.bin/cucumber-js --tags "@algod or @assets or @auction or @kmd or @send or @indexer or @rekey or @send.keyregtxn or @dryrun or @compile or @applications or @indexer.applications or @applications.verified or @indexer.231 or @abi or @c2c" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js
node_modules/.bin/cucumber-js --tags "@algod or @assets or @auction or @kmd or @send or @indexer or @rekey or @send.keyregtxn or @dryrun or @compile or @applications or @indexer.applications or @applications.verified or @indexer.231 or @abi or @c2c or @compile.sourcemap" tests/cucumber/features --require-module ts-node/register --require tests/cucumber/steps/index.js

docker-test:
./tests/cucumber/docker/run_docker.sh
Expand Down
67 changes: 56 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
"js-sha512": "^0.8.0",
"json-bigint": "^1.0.0",
"superagent": "^6.1.0",
"tweetnacl": "^1.0.3"
"tweetnacl": "^1.0.3",
"vlq": "^2.0.4"
},
"optionalDependencies": {
"fsevents": "2.1.2"
},
"devDependencies": {
"@types/json-bigint": "^1.0.0",
Expand Down
5 changes: 3 additions & 2 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ export default class HTTPClient {
async post(
relativePath: string,
data: any,
requestHeaders: Record<string, string> = {}
requestHeaders: Record<string, string> = {},
query?: Query<any>
): Promise<HTTPClientResponse> {
const fullHeaders = {
'content-type': 'application/json',
Expand All @@ -261,7 +262,7 @@ export default class HTTPClient {
const res = await this.bc.post(
relativePath,
HTTPClient.serializeData(data, fullHeaders),
undefined,
query,
fullHeaders
);

Expand Down
8 changes: 7 additions & 1 deletion src/client/v2/algod/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export default class Compile extends JSONRequest {
return `/v2/teal/compile`;
}

sourcemap(map: boolean = true) {
this.query.sourcemap = map;
return this;
}

/**
* Executes compile
* @param headers - A headers object
Expand All @@ -37,7 +42,8 @@ export default class Compile extends JSONRequest {
const res = await this.c.post(
this.path(),
Buffer.from(this.source),
txHeaders
txHeaders,
this.query
);
return res.body;
}
Expand Down
67 changes: 67 additions & 0 deletions src/logic/sourcemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as vlq from 'vlq';

export class SourceMap {
version: number;
sources: string[];
names: string[];
mappings: string;

pcToLine: { [key: number]: number };
lineToPc: { [key: number]: number[] };

constructor({
version,
sources,
names,
mappings,
}: {
version: number;
sources: string[];
names: string[];
mappings: string;
}) {
this.version = version;
this.sources = sources;
this.names = names;
this.mappings = mappings;

if (this.version !== 3)
throw new Error(`Only version 3 is supported, got ${this.version}`);

if (this.mappings === undefined)
throw new Error(
'mapping undefined, cannot build source map without `mapping`'
);

const pcList = this.mappings.split(';').map((m) => {
const decoded = vlq.decode(m);
if (decoded.length > 2) return decoded[2];
return undefined;
});

this.pcToLine = {};
this.lineToPc = {};

let lastLine = 0;
for (const [pc, lineDelta] of pcList.entries()) {
// If the delta is not undefined, the lastLine should be updated with
// lastLine + the delta
if (lineDelta !== undefined) {
lastLine += lineDelta;
}

if (!(lastLine in this.lineToPc)) this.lineToPc[lastLine] = [];

this.lineToPc[lastLine].push(pc);
this.pcToLine[pc] = lastLine;
}
}

getLineForPc(pc: number): number | undefined {
return this.pcToLine[pc];
}

getPcsForLine(line: number): number[] | undefined {
return this.lineToPc[line];
}
}
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ export {
appendSignMultisigTransaction,
multisigAddress,
} from './multisig';
export { SourceMap } from './logic/sourcemap';

export const LogicTemplates = LogicTemplatesCommonJSExport.default;

export * from './dryrun';
Expand Down
51 changes: 51 additions & 0 deletions tests/cucumber/steps/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -5341,6 +5341,57 @@ module.exports = function getSteps(options) {
}
);

Given('a source map json file {string}', async function (srcmap) {
const js = parseJSON(await loadResource(srcmap));
this.sourcemap = new algosdk.SourceMap(js);
});

Then(
'the string composed of pc:line number equals {string}',
function (mapping) {
const buff = Object.entries(this.sourcemap.pcToLine).map(
([pc, line]) => `${pc}:${line}`
);
assert.equal(buff.join(';'), mapping);
}
);

Then(
'getting the line associated with a pc {string} equals {string}',
function (pc, expectedLine) {
const actualLine = this.sourcemap.getLineForPc(parseInt(pc));
assert.equal(actualLine, parseInt(expectedLine));
}
);

Then(
'getting the last pc associated with a line {string} equals {string}',
function (line, expectedPc) {
const actualPcs = this.sourcemap.getPcsForLine(parseInt(line));
assert.equal(actualPcs.pop(), parseInt(expectedPc));
}
);

When(
'I compile a teal program {string} with mapping enabled',
async function (teal) {
const tealSrc = await loadResource(teal);
const compiledResponse = await this.v2Client
.compile(tealSrc)
.sourcemap(true)
.do();
this.rawSourceMap = JSON.stringify(compiledResponse.sourcemap);
}
);

Then(
'the resulting source map is the same as the json {string}',
async function (expectedJsonPath) {
const expected = await loadResource(expectedJsonPath);
assert.equal(this.rawSourceMap, expected.toString().trim());
}
);

if (!options.ignoreReturn) {
return steps;
}
Expand Down

0 comments on commit 4266235

Please sign in to comment.