Asignaciones de origen

Como parte de la salida de AST, el compilador proporciona el rango del código fuente que está representado por el nodo respectivo en el AST. Esto se puede usar para varios propósitos, desde herramientas de análisis estático que informan errores en función del AST hasta herramientas de depuración que resaltan las variables locales y sus usos.

Además, el compilador también puede generar una asignación desde el código de bytes hasta el rango en el código fuente que generó la instrucción. Esto es de nuevo importante para las herramientas de análisis estático que operan a nivel de bytecode y para mostrar la posición actual en el código fuente dentro de un depurador o para el manejo de puntos de interrupción. Este mapeo también contiene otra información, como el tipo de salto y la profundidad del modificador (ver más abajo).

Ambos tipos de asignaciones de origen utilizan identificadores integer para referirse a los archivos de origen. El identificador de un archivo de origen se almacena en output['sources'][sourceName]['id'] donde output es la salida del compilador standard-json analizada como JSON. Para algunas rutinas de utilidad, el compilador genera archivos de origen «internos» que no forman parte de la entrada original, pero a los que se hace referencia desde las asignaciones de origen. Estos archivos de origen junto con sus identificadores pueden obtenerse a través de output['contracts'][sourceName][contractName]['evm']['bytecode']['generatedSources'].

Nota

En el caso de instrucciones que no están asociadas a ningún archivo fuente en particular, la asignación de origen asigna un identificador integer de -1. Esto puede ocurrir para secciones de código de bytes que provienen de sentencias de ensamblaje en línea generadas por el compilador.

Las asignaciones de origen dentro del AST utilizan la siguiente notación:

s:l:f

Donde s es el byte-offset al inicio del rango en el archivo de origen, l es la longitud del rango de origen en bytes y f es el índice de origen mencionado anteriormente.

La codificación en la asignación de origen para el código de bytes es más complicada: Es una lista de s:l:f:j:m separados por ;. Cada uno de estos elementos corresponde a una instrucción, es decir, no se puede utilizar el offset de bytes, sino que hay que utilizar el offset de instrucciones (las instrucciones push son más largas que un solo byte). Los campos s, l y f son como los anteriores. j puede ser i, o o - lo que significa que una instrucción de salto entra en una función, vuelve de una función o es un salto normal como parte de, por ejemplo, un loop. El último campo, m, es un integer que denota la «profundidad del modificador». Esta profundidad se incrementa cada vez que se introduce el marcador de posición (_) y disminuye cuando se vuelve a dejar. Esto permite a los debuggers rastrear casos complicados como el mismo modificador siendo utilizado dos o múltiples veces en marcadores de posición siendo usadas en un solo modificador.

Para comprimir estas asignaciones de origen, especialmente para bytecode, se utilizan las siguientes reglas:

  • Si un campo está vacío, se utiliza el valor del elemento precedente.

  • Si falta un :, todos los campos siguientes se consideran vacíos.

Esto significa que las siguientes asignaciones de origen representan la misma información:

1:2:1;1:9:1;2:1:2;2:1:2;2:1:2

1:2:1;:9;2:1:2;;

Es importante tener en cuenta que cuando se utiliza verbatim, las asignaciones de origen no serán válidas: El builtin se considera una única instrucción en lugar de potencialmente múltiples.