Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DevTools: adding source map decoder #352

Merged
merged 10 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
unit:
mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application"
mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application or @unit.sourcemap"

integration:
mvn test \
-Dtest=com.algorand.algosdk.integration.RunCucumberIntegrationTest \
-Dcucumber.filter.tags="@algod or @assets or @auction or @kmd or @send or @send.keyregtxn or @indexer or @rekey_v1 or @applications.verified or @applications or @compile or @dryrun or @indexer.applications or @indexer.231 or @abi or @c2c"
-Dcucumber.filter.tags="@algod or @assets or @auction or @kmd or @send or @send.keyregtxn or @indexer or @rekey_v1 or @applications.verified or @applications or @compile or @dryrun or @indexer.applications or @indexer.231 or @abi or @c2c or @compile.sourcemap"

docker-test:
./run_integration_tests.sh
112 changes: 112 additions & 0 deletions src/main/java/com/algorand/algosdk/logic/SourceMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.algorand.algosdk.logic;

import java.lang.Integer;
import java.util.ArrayList;
import java.util.HashMap;

/**
* SourceMap class provides parser for source map from
* algod compile endpoint
*/
public class SourceMap {

public int version;
public String file;
public String[] sources;
public String[] names;
public String mappings;

public HashMap<Integer, Integer> pcToLine;
public HashMap<Integer, ArrayList<Integer>> lineToPc;

public SourceMap(HashMap<String,Object> sourceMap) {
int version = (int) sourceMap.get("version");
if(version != 3){
throw new IllegalArgumentException("Only source map version 3 is supported");
}
this.version = version;

this.file = (String) sourceMap.get("file");
this.mappings = (String) sourceMap.get("mappings");

this.lineToPc = new HashMap<>();
this.pcToLine = new HashMap<>();

Integer lastLine = 0;
String[] vlqs = this.mappings.split(";");
for(int i=0; i<vlqs.length; i++){
ArrayList<Integer> vals = VLQDecoder.decodeSourceMapLine(vlqs[i]);

// If the vals length >= 3 the lineDelta
if(vals.size() >= 3){
lastLine = lastLine + vals.get(2);
}

if(!this.lineToPc.containsKey(lastLine)){
this.lineToPc.put(lastLine, new ArrayList<Integer>());
}

ArrayList<Integer> currList = this.lineToPc.get(lastLine);
currList.add(i);
this.lineToPc.put(lastLine, currList);

this.pcToLine.put(i, lastLine);
}

}

public Integer getLineForPc(Integer pc) {
if(!this.pcToLine.containsKey(pc)){
return 0;
}
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
return this.pcToLine.get(pc);
}

public ArrayList<Integer> getPcsForLine(Integer line) {
if(!this.pcToLine.containsKey(line)){
return new ArrayList<Integer>();
}
return this.lineToPc.get(line);
}

private static class VLQDecoder {
michaeldiamant marked this conversation as resolved.
Show resolved Hide resolved

private static final String b64table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
private static final int vlqShiftSize = 5;
private static final int vlqFlag = 1 << vlqShiftSize;
private static final int vlqMask = vlqFlag - 1;

public static ArrayList<Integer> decodeSourceMapLine(String vlq) {

ArrayList<Integer> results = new ArrayList<>();
int value = 0;
int shift = 0;

for(int i=0; i<vlq.length(); i++){
int digit = b64table.indexOf(vlq.charAt(i));

value |= (digit & vlqMask) << shift;

if((digit & vlqFlag) > 0) {
shift += vlqShiftSize;
continue;
}

if((value&1)>0){
value = (value >> 1) * -1;
}else{
value = value >> 1;
}

results.add(value);

// Reset
value = 0;
shift = 0;
}

return results;
}
}

}
27 changes: 27 additions & 0 deletions src/test/java/com/algorand/algosdk/integration/Stepdefs.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
import com.algorand.algosdk.transaction.Transaction;
import com.algorand.algosdk.util.AlgoConverter;
import com.algorand.algosdk.util.Encoder;
import com.algorand.algosdk.util.ResourceUtils;
import com.algorand.algosdk.v2.client.common.Response;
import com.algorand.algosdk.v2.client.model.CompileResponse;
import com.algorand.algosdk.v2.client.model.DryrunResponse;
import com.algorand.algosdk.v2.client.model.DryrunRequest;
import com.algorand.algosdk.v2.client.model.DryrunSource;

import com.fasterxml.jackson.core.JsonProcessingException;

import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
Expand All @@ -32,6 +34,7 @@
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
Expand All @@ -40,7 +43,9 @@
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;

Expand Down Expand Up @@ -1475,4 +1480,26 @@ public void i_get_execution_result(String result) throws Exception {
assertThat(msgs.size()).isGreaterThan(0);
assertThat(msgs.get(msgs.size() - 1)).isEqualTo(result);
}


@When("I compile a teal program {string} with mapping enabled")
public void i_compile_a_teal_program_with_mapping_enabled(String tealPath) throws Exception {
byte[] tealProgram = ResourceUtils.loadResource(tealPath);
this.compileResponse = aclv2.TealCompile().source(tealProgram).sourcemap(true).execute();
}


@Then("the resulting source map is the same as the json {string}")
public void the_resulting_source_map_is_the_same_as_the_json(String jsonPath) throws Exception {
String[] fields = {"version", "sources", "names", "mapping", "mappings"};
String srcMapStr = new String(ResourceUtils.readResource(jsonPath), StandardCharsets.UTF_8);

HashMap<String, Object> expectedMap = new HashMap<>(Encoder.decodeFromJson(srcMapStr, Map.class));
HashMap<String, Object> actualMap = this.compileResponse.body().sourcemap;

for(String field: fields){
assertThat(actualMap.get(field)).isEqualTo(expectedMap.get(field));
}
}

}
60 changes: 60 additions & 0 deletions src/test/java/com/algorand/algosdk/unit/TestSourceMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.algorand.algosdk.unit;

import com.algorand.algosdk.logic.SourceMap;
import com.algorand.algosdk.util.Encoder;
import com.algorand.algosdk.util.ResourceUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;

import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import static org.assertj.core.api.Assertions.assertThat;


public class TestSourceMap {
SourceMap srcMap;

@Given("a source map json file {string}")
public void a_source_map_json_file(String srcMapPath) throws IOException {
String srcMapStr = new String(ResourceUtils.readResource(srcMapPath), StandardCharsets.UTF_8);
HashMap<String, Object> map = new HashMap<>(Encoder.decodeFromJson(srcMapStr, Map.class));
this.srcMap = new SourceMap(map);
}

@Then("the string composed of pc:line number equals {string}")
public void the_string_composed_of_pc_line_number_equals(String expected) {
List<String> strs = new ArrayList<>();
for(Map.Entry<Integer,Integer> entry: this.srcMap.pcToLine.entrySet()){
strs.add(String.format("%d:%d", entry.getKey(), entry.getValue()));
}

String actual = StringUtils.join(strs, ";");
assertThat(actual).isEqualTo(expected);
}

@Then("getting the last pc associated with a line {string} equals {string}")
public void getting_the_last_pc_associated_with_a_line_equals(String lineStr, String pcStr) {
Integer line = Integer.valueOf(lineStr);
Integer pc = Integer.valueOf(pcStr);

ArrayList<Integer> actualPcs = this.srcMap.getPcsForLine(line);
assertThat(actualPcs.get(actualPcs.size() - 1)).isEqualTo(pc);
}


@Then("getting the line associated with a pc {string} equals {string}")
public void getting_the_line_associated_with_a_pc_equals(String pcStr, String lineStr) {
Integer line = Integer.valueOf(lineStr);
Integer pc = Integer.valueOf(pcStr);

Integer actualLine = this.srcMap.getLineForPc(pc);
assertThat(actualLine).isEqualTo(line);
}

}