Home Reference Source

lib/data/selectors/index.js

import debugModule from "debug";
const debug = debugModule("debugger:data:selectors");

import { createSelectorTree, createLeaf } from "reselect-tree";
import jsonpointer from "json-pointer";

import ast from "lib/ast/selectors";
import evm from "lib/evm/selectors";
import solidity from "lib/solidity/selectors";

import decode from "../decode";
import * as decodeUtils from "../decode/utils";

import { BigNumber } from "bignumber.js";

function createStateSelectors({ stack, memory, storage }) {
  return {
    /**
     * .stack
     */
    stack: createLeaf(
      [stack],

      (words) => (words || []).map(
        (word) => decodeUtils.toBytes(decodeUtils.toBigNumber(word, decodeUtils.WORD_SIZE))
      )
    ),

    /**
     * .memory
     */
    memory: createLeaf(
      [memory],

      (words) => new Uint8Array(
        (words.join("").match(/.{1,2}/g) || [])
          .map( (byte) => parseInt(byte, 16) )
      )
    ),

    /**
     * .storage
     */
    storage: createLeaf(
      [storage],

      (mapping) => Object.assign(
        {}, ...Object.entries(mapping).map( ([ address, word ]) =>
          ({
            [`0x${address}`]: new Uint8Array(
              (word.match(/.{1,2}/g) || [])
                .map( (byte) => parseInt(byte, 16) )
            )
          })
        )
      )
    )
  };
}

const data = createSelectorTree({
  state: (state) => state.data,

  /**
   * data.views
   */
  views: {
    ast: createLeaf(
      [ast.current], (tree) => tree
    ),

    atLastInstructionForSourceRange: createLeaf(
      [solidity.current.isSourceRangeFinal], (final) => final
    ),

    /**
     * data.views.scopes
     */
    scopes: {

      /**
       * data.views.scopes.inlined
       */
      inlined: createLeaf(
        ["/info/scopes", solidity.info.sources],

        (scopes, sources) => Object.assign({},
          ...Object.entries(scopes).map(
            ([id, entry]) => ({
              [id]: {
                ...entry,

                definition: jsonpointer.get(
                  sources[entry.sourceId].ast, entry.pointer
                )
              }
            })
          )
        )
      )
    },

    /**
     * data.views.decoder
     *
     * selector returns (ast node definition, data reference) => value
     */
    decoder: createLeaf(
      ["/views/scopes/inlined", "/current/state"],

      (scopes, state) => {
        return (definition, ref) => decode(definition, ref, state, scopes)
      }
    )
  },

  /**
   * data.info
   */
  info: {

    /**
     * data.info.scopes
     */
    scopes: createLeaf(["/state"], (state) => state.info.scopes.byId)
  },

  /**
   * data.proc
   */
  proc: {

    /**
     * data.proc.assignments
     */
    assignments: createLeaf(["/state"], (state) => state.proc.assignments.byId)
  },

  /**
   * data.current
   */
  current: {
    /**
     *
     * data.current.scope
     */
    scope: {

      /**
       * data.current.scope.id
       */
      id: createLeaf(
        [ast.current.node], (node) => node.id
      )
    },

    /**
     * data.current.state
     */
    state: createStateSelectors(evm.current.state),

    /**
     * data.current.identifiers (namespace)
     */
    identifiers: {

      /**
       * data.current.identifiers (selector)
       *
       * returns identifers and corresponding definition node ID
       */
      _: createLeaf(
        [
          "/views/scopes/inlined",
          "/current/scope",
        ],

        (scopes, scope) => {
          let cur = scope.id;
          let variables = {};

          do {
            variables = Object.assign(
              variables,
              ...(scopes[cur].variables || [])
                .filter( (v) => variables[v.name] == undefined )
                .map( (v) => ({ [v.name]: v.id }) )
            );

            cur = scopes[cur].parentId;
          } while (cur != null);

          return variables;
        }
      ),

      /**
       * data.current.identifiers.definitions
       *
       * current variable definitions
       */
      definitions: createLeaf(
        [
          "/views/scopes/inlined",
          "./_"
        ],

        (scopes, identifiers) => Object.assign({},
          ...Object.entries(identifiers)
            .map( ([identifier, id]) => {
              let { definition } = scopes[id];

              return { [identifier]: definition };
            })
        )
      ),

      /**
       * data.current.identifiers.refs
       *
       * current variables' value refs
       */
      refs: createLeaf(
        [
          "/proc/assignments",
          "./_"
        ],

        (assignments, identifiers) => Object.assign({},
          ...Object.entries(identifiers)
            .map( ([identifier, id]) => {
              let { ref } = (assignments[id] || {})
              if (!ref) { return undefined };

              return {
                [identifier]: ref
              };
            })
        )
      ),

      decoded: createLeaf(
        [
          "/views/decoder",
          "./definitions",
          "./refs",
        ],

        (decode, definitions, refs) => Object.assign({},
          ...Object.entries(refs)
            .map( ([identifier, ref]) => ({
              [identifier]: decode(definitions[identifier], ref)
            }) )
        )
      ),

      native: createLeaf(['./decoded'], decodeUtils.cleanBigNumbers)
    }
  },

  /**
   * data.next
   */
  next: {

    /**
     * data.next.state
     */
    state: createStateSelectors(evm.next.state)
  }
});

export default data;