Skip to content

Commit 5db404e

Browse files
authored
DevTools: adding source map decoder (#352)
* adding source map decoder
1 parent 369b9a0 commit 5db404e

File tree

4 files changed

+210
-2
lines changed

4 files changed

+210
-2
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
unit:
2-
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"
2+
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"
33

44
integration:
55
mvn test \
66
-Dtest=com.algorand.algosdk.integration.RunCucumberIntegrationTest \
7-
-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"
7+
-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"
88

99
docker-test:
1010
./run_integration_tests.sh
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package com.algorand.algosdk.logic;
2+
3+
import java.lang.Integer;
4+
import java.util.ArrayList;
5+
import java.util.HashMap;
6+
7+
/**
8+
* SourceMap class provides parser for source map from
9+
* algod compile endpoint
10+
*/
11+
public class SourceMap {
12+
13+
public int version;
14+
public String file;
15+
public String[] sources;
16+
public String[] names;
17+
public String mappings;
18+
19+
public HashMap<Integer, Integer> pcToLine;
20+
public HashMap<Integer, ArrayList<Integer>> lineToPc;
21+
22+
public SourceMap(HashMap<String,Object> sourceMap) {
23+
int version = (int) sourceMap.get("version");
24+
if(version != 3){
25+
throw new IllegalArgumentException("Only source map version 3 is supported");
26+
}
27+
this.version = version;
28+
29+
this.file = (String) sourceMap.get("file");
30+
this.mappings = (String) sourceMap.get("mappings");
31+
32+
this.lineToPc = new HashMap<>();
33+
this.pcToLine = new HashMap<>();
34+
35+
Integer lastLine = 0;
36+
String[] vlqs = this.mappings.split(";");
37+
for(int i=0; i<vlqs.length; i++){
38+
ArrayList<Integer> vals = VLQDecoder.decodeSourceMapLine(vlqs[i]);
39+
40+
// If the vals length >= 3 the lineDelta
41+
if(vals.size() >= 3){
42+
lastLine = lastLine + vals.get(2);
43+
}
44+
45+
if(!this.lineToPc.containsKey(lastLine)){
46+
this.lineToPc.put(lastLine, new ArrayList<Integer>());
47+
}
48+
49+
ArrayList<Integer> currList = this.lineToPc.get(lastLine);
50+
currList.add(i);
51+
this.lineToPc.put(lastLine, currList);
52+
53+
this.pcToLine.put(i, lastLine);
54+
}
55+
56+
}
57+
58+
/**
59+
* Returns the Integer line number for the passed PC or null if not found
60+
* @param pc the pc (program counter) of the assembled file
61+
* @return the line number or null if not found
62+
*/
63+
public Integer getLineForPc(Integer pc) {
64+
return this.pcToLine.get(pc);
65+
}
66+
67+
/**
68+
* Returns the List of PCs for the passed line number
69+
* @param line the line number of the source file
70+
* @return the list of PCs that line generated or empty array if not found
71+
*/
72+
public ArrayList<Integer> getPcsForLine(Integer line) {
73+
if(!this.pcToLine.containsKey(line)){
74+
return new ArrayList<Integer>();
75+
}
76+
return this.lineToPc.get(line);
77+
}
78+
79+
private static class VLQDecoder {
80+
// VLQDecoder for decoding the VLQ values returned for source map
81+
// based on the decoder implementation here: https://github.com/algorand/go-algorand-sdk/blob/develop/logic/source_map.go
82+
83+
private static final String b64table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
84+
private static final int vlqShiftSize = 5;
85+
private static final int vlqFlag = 1 << vlqShiftSize;
86+
private static final int vlqMask = vlqFlag - 1;
87+
88+
public static ArrayList<Integer> decodeSourceMapLine(String vlq) {
89+
90+
ArrayList<Integer> results = new ArrayList<>();
91+
int value = 0;
92+
int shift = 0;
93+
94+
for(int i=0; i<vlq.length(); i++){
95+
int digit = b64table.indexOf(vlq.charAt(i));
96+
97+
value |= (digit & vlqMask) << shift;
98+
99+
if((digit & vlqFlag) > 0) {
100+
shift += vlqShiftSize;
101+
continue;
102+
}
103+
104+
if((value&1)>0){
105+
value = (value >> 1) * -1;
106+
}else{
107+
value = value >> 1;
108+
}
109+
110+
results.add(value);
111+
112+
// Reset
113+
value = 0;
114+
shift = 0;
115+
}
116+
117+
return results;
118+
}
119+
}
120+
121+
}

src/test/java/com/algorand/algosdk/integration/Stepdefs.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
import com.algorand.algosdk.transaction.Transaction;
1818
import com.algorand.algosdk.util.AlgoConverter;
1919
import com.algorand.algosdk.util.Encoder;
20+
import com.algorand.algosdk.util.ResourceUtils;
2021
import com.algorand.algosdk.v2.client.common.Response;
2122
import com.algorand.algosdk.v2.client.model.CompileResponse;
2223
import com.algorand.algosdk.v2.client.model.DryrunResponse;
2324
import com.algorand.algosdk.v2.client.model.DryrunRequest;
2425
import com.algorand.algosdk.v2.client.model.DryrunSource;
2526

2627
import com.fasterxml.jackson.core.JsonProcessingException;
28+
2729
import io.cucumber.java.en.Given;
2830
import io.cucumber.java.en.Then;
2931
import io.cucumber.java.en.When;
@@ -32,6 +34,7 @@
3234
import java.io.*;
3335
import java.math.BigDecimal;
3436
import java.math.BigInteger;
37+
import java.nio.charset.StandardCharsets;
3538
import java.nio.file.Path;
3639
import java.nio.file.Paths;
3740
import java.security.GeneralSecurityException;
@@ -40,7 +43,9 @@
4043
import java.util.Arrays;
4144
import java.util.ArrayList;
4245
import java.util.Collections;
46+
import java.util.HashMap;
4347
import java.util.List;
48+
import java.util.Map;
4449
import java.util.Set;
4550
import java.util.concurrent.ThreadLocalRandom;
4651

@@ -1475,4 +1480,26 @@ public void i_get_execution_result(String result) throws Exception {
14751480
assertThat(msgs.size()).isGreaterThan(0);
14761481
assertThat(msgs.get(msgs.size() - 1)).isEqualTo(result);
14771482
}
1483+
1484+
1485+
@When("I compile a teal program {string} with mapping enabled")
1486+
public void i_compile_a_teal_program_with_mapping_enabled(String tealPath) throws Exception {
1487+
byte[] tealProgram = ResourceUtils.loadResource(tealPath);
1488+
this.compileResponse = aclv2.TealCompile().source(tealProgram).sourcemap(true).execute();
1489+
}
1490+
1491+
1492+
@Then("the resulting source map is the same as the json {string}")
1493+
public void the_resulting_source_map_is_the_same_as_the_json(String jsonPath) throws Exception {
1494+
String[] fields = {"version", "sources", "names", "mapping", "mappings"};
1495+
String srcMapStr = new String(ResourceUtils.readResource(jsonPath), StandardCharsets.UTF_8);
1496+
1497+
HashMap<String, Object> expectedMap = new HashMap<>(Encoder.decodeFromJson(srcMapStr, Map.class));
1498+
HashMap<String, Object> actualMap = this.compileResponse.body().sourcemap;
1499+
1500+
for(String field: fields){
1501+
assertThat(actualMap.get(field)).isEqualTo(expectedMap.get(field));
1502+
}
1503+
}
1504+
14781505
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.algorand.algosdk.unit;
2+
3+
import com.algorand.algosdk.logic.SourceMap;
4+
import com.algorand.algosdk.util.Encoder;
5+
import com.algorand.algosdk.util.ResourceUtils;
6+
7+
import java.io.IOException;
8+
import java.nio.charset.StandardCharsets;
9+
import java.util.ArrayList;
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
import org.apache.commons.lang3.StringUtils;
14+
15+
import io.cucumber.java.en.Given;
16+
import io.cucumber.java.en.Then;
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
20+
public class TestSourceMap {
21+
SourceMap srcMap;
22+
23+
@Given("a source map json file {string}")
24+
public void a_source_map_json_file(String srcMapPath) throws IOException {
25+
String srcMapStr = new String(ResourceUtils.readResource(srcMapPath), StandardCharsets.UTF_8);
26+
HashMap<String, Object> map = new HashMap<>(Encoder.decodeFromJson(srcMapStr, Map.class));
27+
this.srcMap = new SourceMap(map);
28+
}
29+
30+
@Then("the string composed of pc:line number equals {string}")
31+
public void the_string_composed_of_pc_line_number_equals(String expected) {
32+
List<String> strs = new ArrayList<>();
33+
for(Map.Entry<Integer,Integer> entry: this.srcMap.pcToLine.entrySet()){
34+
strs.add(String.format("%d:%d", entry.getKey(), entry.getValue()));
35+
}
36+
37+
String actual = StringUtils.join(strs, ";");
38+
assertThat(actual).isEqualTo(expected);
39+
}
40+
41+
@Then("getting the last pc associated with a line {string} equals {string}")
42+
public void getting_the_last_pc_associated_with_a_line_equals(String lineStr, String pcStr) {
43+
Integer line = Integer.valueOf(lineStr);
44+
Integer pc = Integer.valueOf(pcStr);
45+
46+
ArrayList<Integer> actualPcs = this.srcMap.getPcsForLine(line);
47+
assertThat(actualPcs.get(actualPcs.size() - 1)).isEqualTo(pc);
48+
}
49+
50+
51+
@Then("getting the line associated with a pc {string} equals {string}")
52+
public void getting_the_line_associated_with_a_pc_equals(String pcStr, String lineStr) {
53+
Integer line = Integer.valueOf(lineStr);
54+
Integer pc = Integer.valueOf(pcStr);
55+
56+
Integer actualLine = this.srcMap.getLineForPc(pc);
57+
assertThat(actualLine).isEqualTo(line);
58+
}
59+
60+
}

0 commit comments

Comments
 (0)