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

Dev Tools: Source map decoder #353

Merged
merged 38 commits into from
Jul 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
240759e
Merge branch 'release/v1.10.0b1'
algobarb Feb 15, 2022
9d5ec1b
Merge branch 'release/v1.10.0'
onetechnical Mar 2, 2022
23354e9
Merge branch 'release/v1.11.0b1'
onetechnical Mar 18, 2022
51e0364
Merge branch 'release/v1.11.0'
algobarb Mar 23, 2022
e6f5aa9
Merge branch 'release/v1.12.0'
algojack Apr 21, 2022
896d2aa
Merge branch 'release/v1.13.0'
onetechnical May 2, 2022
de80435
Merge branch 'release/v1.13.1'
onetechnical May 4, 2022
760d892
Merge branch 'release/v1.14.0'
onetechnical Jun 2, 2022
671aeb6
Merge branch 'release/v1.15.0'
algojack Jun 16, 2022
c03a53d
adding src map decoder
barnjamin Jun 20, 2022
cb7a735
fmt
barnjamin Jun 20, 2022
5a1bf5d
reorg, fmt, add comments
barnjamin Jun 20, 2022
5ca54c6
Merge branch 'src-map-decoder' into develop
barnjamin Jun 20, 2022
5bc92ab
remove comments from src map
barnjamin Jun 23, 2022
f547df8
move SourceMap to its own file, add source_map arg in client for requ…
barnjamin Jun 24, 2022
a617d2c
adding tests for parsing source map from file
barnjamin Jun 24, 2022
abbf604
Merge branch 'develop' of github.com:algorand/py-algorand-sdk into de…
barnjamin Jun 24, 2022
58e60f0
Merge branch 'develop' into src-map-decoder
barnjamin Jun 24, 2022
a968807
fmt
barnjamin Jun 24, 2022
92d197c
implement new test
barnjamin Jun 24, 2022
d4618d6
fmt
barnjamin Jun 24, 2022
d0ec5c1
cr
barnjamin Jun 24, 2022
90333ca
cr
barnjamin Jun 24, 2022
c217a96
Merge branch 'src-map-decoder' of github.com:algorand/py-algorand-sdk…
barnjamin Jun 24, 2022
fe2aef6
cr changes, mostly naming fixes
barnjamin Jun 27, 2022
1d4c007
removing delimited, adding exception if version != 3, checking for ma…
barnjamin Jun 28, 2022
8ed7979
updating to new format
barnjamin Jul 1, 2022
94b59c0
remove initialized 0s
barnjamin Jul 1, 2022
be41089
fmt
barnjamin Jul 1, 2022
14b6658
Update algosdk/source_map.py
barnjamin Jul 1, 2022
7c4309c
use on dict lookup, returning None if not set
barnjamin Jul 11, 2022
ab087ff
Merge branch 'src-map-decoder' of github.com:algorand/py-algorand-sdk…
barnjamin Jul 11, 2022
ece278c
adding commments about None line and tweaking logic to append to the …
barnjamin Jul 11, 2022
689ad74
Update algosdk/source_map.py
barnjamin Jul 11, 2022
5a72f99
adding new tests
barnjamin Jul 12, 2022
e5a3ee8
Merge branch 'src-map-decoder' of github.com:algorand/py-algorand-sdk…
barnjamin Jul 12, 2022
0339df9
fmt
barnjamin Jul 12, 2022
c7cd30a
revert to master
barnjamin Jul 13, 2022
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,8 +1,8 @@
UNITS = "@unit.abijson or @unit.abijson.byname or @unit.algod or @unit.algod.ledger_refactoring or @unit.applications or @unit.atc_method_args or @unit.atomic_transaction_composer or @unit.dryrun or @unit.dryrun.trace.application or @unit.feetest or @unit.indexer or @unit.indexer.ledger_refactoring or @unit.indexer.logs or @unit.offline or @unit.rekey or @unit.transactions.keyreg or @unit.responses or @unit.responses.231 or @unit.tealsign or @unit.transactions or @unit.transactions.payment or @unit.responses.unlimited_assets"
UNITS = "@unit.abijson or @unit.abijson.byname or @unit.algod or @unit.algod.ledger_refactoring or @unit.applications or @unit.atc_method_args or @unit.atomic_transaction_composer or @unit.dryrun or @unit.dryrun.trace.application or @unit.feetest or @unit.indexer or @unit.indexer.ledger_refactoring or @unit.indexer.logs or @unit.offline or @unit.rekey or @unit.transactions.keyreg or @unit.responses or @unit.responses.231 or @unit.tealsign or @unit.transactions or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.sourcemap"
unit:
behave --tags=$(UNITS) tests -f progress2

barnjamin marked this conversation as resolved.
Show resolved Hide resolved
INTEGRATIONS = "@abi or @algod or @applications or @applications.verified or @assets or @auction or @c2c or @compile or @dryrun or @dryrun.testing or @indexer or @indexer.231 or @indexer.applications or @kmd or @rekey or @send.keyregtxn or @send"
INTEGRATIONS = "@abi or @algod or @applications or @applications.verified or @assets or @auction or @c2c or @compile or @dryrun or @dryrun.testing or @indexer or @indexer.231 or @indexer.applications or @kmd or @rekey or @send.keyregtxn or @send or @compile.sourcemap"
integration:
behave --tags=$(INTEGRATIONS) tests -f progress2

Expand Down
1 change: 1 addition & 0 deletions algosdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
from . import v2client
from . import wallet
from . import wordlist
from . import source_map

name = "algosdk"
8 changes: 8 additions & 0 deletions algosdk/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,11 @@ def __init__(self, msg):
class AtomicTransactionComposerError(Exception):
def __init__(self, msg):
super().__init__(msg)


class SourceMapVersionError(Exception):
def __init__(self, version):
Exception.__init__(
self,
"Only SourceMap version 3 is supported, got: {}".format(version),
)
85 changes: 85 additions & 0 deletions algosdk/source_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from typing import Dict, Any, List, Tuple

from algosdk.error import SourceMapVersionError


class SourceMap:
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
"""
Decodes a VLQ-encoded source mapping between PC values and TEAL source code lines.
Spec available here: https://sourcemaps.info/spec.html

Args:
source_map (dict(str, Any)): source map JSON from algod
"""

def __init__(self, source_map: Dict[str, Any]):

self.version: int = source_map["version"]
michaeldiamant marked this conversation as resolved.
Show resolved Hide resolved

if self.version != 3:
raise SourceMapVersionError(self.version)

self.sources: List[str] = source_map["sources"]

self.mappings: str = source_map["mappings"]

pc_list = [
_decode_int_value(raw_val) for raw_val in self.mappings.split(";")
]

self.pc_to_line: Dict[int, int] = {}
self.line_to_pc: Dict[int, List[int]] = {}

last_line = 0
for index, line_delta in enumerate(pc_list):
# line_delta is None if the line number has not changed
# or if the line is empty
if line_delta is not None:
last_line = last_line + line_delta

if last_line not in self.line_to_pc:
self.line_to_pc[last_line] = []

self.line_to_pc[last_line].append(index)
self.pc_to_line[index] = last_line

def get_line_for_pc(self, pc: int) -> int:
return self.pc_to_line.get(pc, None)

def get_pcs_for_line(self, line: int) -> List[int]:
return self.line_to_pc.get(line, None)


def _decode_int_value(value: str) -> int:
# Mappings may have up to 5 segments:
# Third segment represents the zero-based starting line in the original source represented.
decoded_value = _base64vlq_decode(value)
return decoded_value[2] if decoded_value else None
barnjamin marked this conversation as resolved.
Show resolved Hide resolved


"""
Source taken from: https://gist.github.com/mjpieters/86b0d152bb51d5f5979346d11005588b
"""

barnjamin marked this conversation as resolved.
Show resolved Hide resolved
_b64chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
_b64table = [None] * (max(_b64chars) + 1)
for i, b in enumerate(_b64chars):
_b64table[b] = i

shiftsize, flag, mask = 5, 1 << 5, (1 << 5) - 1


def _base64vlq_decode(vlqval: str) -> Tuple[int]:
"""Decode Base64 VLQ value"""
results = []
shift = value = 0
# use byte values and a table to go from base64 characters to integers
for v in map(_b64table.__getitem__, vlqval.encode("ascii")):
value += (v & mask) << shift
if v & flag:
shift += shiftsize
continue
# determine sign and add to results
results.append((value >> 1) * (-1 if value & 1 else 1))
shift = value = 0
return results
6 changes: 3 additions & 3 deletions algosdk/v2client/algod.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def suggested_params(self, **kwargs):
res["min-fee"],
)

def compile(self, source, **kwargs):
def compile(self, source, source_map=False, **kwargs):
"""
Compile TEAL source with remote algod.

Expand All @@ -365,9 +365,9 @@ def compile(self, source, **kwargs):
{"Content-Type": "application/x-binary"},
)
kwargs["headers"] = headers

params = {"sourcemap": source_map}
return self.algod_request(
"POST", req, data=source.encode("utf-8"), **kwargs
"POST", req, params=params, data=source.encode("utf-8"), **kwargs
)

def dryrun(self, drr, **kwargs):
Expand Down
51 changes: 45 additions & 6 deletions tests/steps/other_v2_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@
from glom import glom
import parse

from algosdk import (
dryrun_results,
encoding,
error,
mnemonic,
)
from algosdk import dryrun_results, encoding, error, mnemonic, source_map
from algosdk.error import AlgodHTTPError
from algosdk.future import transaction
from algosdk.v2client import *
Expand Down Expand Up @@ -1707,3 +1702,47 @@ def glom_app_eval_delta(context, i, path, field):
assert field == str(
actual_field
), f"path [{path}] expected value [{field}] but got [{actual_field}] instead"


@given('a source map json file "{sourcemap_file}"')
def parse_source_map(context, sourcemap_file):
jsmap = json.loads(load_resource(sourcemap_file, is_binary=False))
context.source_map = source_map.SourceMap(jsmap)


@then('the string composed of pc:line number equals "{pc_to_line}"')
def check_source_map(context, pc_to_line):
buff = [
f"{pc}:{line}" for pc, line in context.source_map.pc_to_line.items()
]
actual = ";".join(buff)
assert actual == pc_to_line, f"expected {pc_to_line} got {actual}"


@then('getting the line associated with a pc "{pc}" equals "{line}"')
def check_pc_to_line(context, pc, line):

actual_line = context.source_map.get_line_for_pc(int(pc))
assert actual_line == int(line), f"expected line {line} got {actual_line}"


@then('getting the last pc associated with a line "{line}" equals "{pc}"')
def check_line_to_pc(context, line, pc):
actual_pcs = context.source_map.get_pcs_for_line(int(line))
assert actual_pcs[-1] == int(pc), f"expected pc {pc} got {actual_pcs[-1]}"


@when('I compile a teal program "{teal}" with mapping enabled')
def check_compile_mapping(context, teal):
data = load_resource(teal)
source = data.decode("utf-8")
response = context.app_acl.compile(source, source_map=True)
context.raw_source_map = json.dumps(
response["sourcemap"], separators=(",", ":")
)


@then('the resulting source map is the same as the json "{sourcemap}"')
def check_mapping_equal(context, sourcemap):
expected = load_resource(sourcemap).decode("utf-8").strip()
assert context.raw_source_map == expected