<commit_before>import debugModule from "debug";
const debug = debugModule("debugger:data:sagas");

import { put, takeEvery, select, call, putResolve } from "redux-saga/effects";

import { prefixName, stableKeccak256 } from "lib/helpers";

import { TICK } from "lib/trace/actions";
import * as actions from "../actions";
import * as trace from "lib/trace/sagas";

import data from "../selectors";

import * as DecodeUtils from "truffle-decode-utils";
import { getStorageAllocations, readStack, storageSize } from "truffle-decoder";
import BN from "bn.js";

export function* scope(nodeId, pointer, parentId, sourceId) {
  yield putResolve(actions.scope(nodeId, pointer, parentId, sourceId));
}

export function* declare(node) {
  yield putResolve(actions.declare(node));
}

export function* defineType(node) {
  yield putResolve(actions.defineType(node));
}

function* tickSaga() {
  debug("got TICK");

  yield* variablesAndMappingsSaga();
  yield* trace.signalTickSagaCompletion();
}

function* variablesAndMappingsSaga() {
  let node = (yield select(data.views.ast)).node;
  let decode = yield select(data.views.decoder);
  let scopes = yield select(data.views.scopes.inlined);
  let referenceDeclarations = yield select(data.views.referenceDeclarations);
  let allocations = yield select(data.info.allocations.storage);
  let currentAssignments = yield select(data.proc.assignments);
  let mappedPaths = yield select(data.proc.mappedPaths);
  let currentDepth = yield select(data.current.functionDepth);
  let address = yield select(data.current.address); //may be undefined
  let dummyAddress = yield select(data.current.dummyAddress);

  let stack = yield select(data.next.state.stack);
  if (!stack) {
    return;
  }

  let top = stack.length - 1;
  var assignment, assignments, baseExpression, slot, path;

  if (!node) {
    return;
  }

  // stack is only ready for interpretation after the last step of each
  // source range
  //
  // the data module always looks at the result of a particular opcode
  // (i.e., the following trace step's stack/memory/storage), so this
  // asserts that the _current_ operation is the final one before
  // proceeding
  if (!(yield select(data.views.atLastInstructionForSourceRange))) {
    return;
  }

  switch (node.nodeType) {
    case "FunctionDefinition":
      let parameters = node.parameters.parameters;
      //note that we do *not* include return parameters, since those are
      //handled by the VariableDeclaration case (no, I don't know why it
      //works out that way)
      let reverseParameters = parameters.slice().reverse();
      //reverse is in-place, so we use slice() to clone first
      debug("reverseParameters %o", parameters);

      let currentPosition = top;
      assignments = { byId: {} };

      for (let parameter of reverseParameters) {
        let words = DecodeUtils.Definition.stackSize(parameter);
        let pointer = {
          stack: {
            from: currentPosition - words + 1,
            to: currentPosition
          }
        };
        let assignment = makeAssignment(
          { astId: parameter.id, stackframe: currentDepth },
          pointer
        );
        assignments.byId[assignment.id] = assignment;
        currentPosition -= words;
      }

      debug("Function definition case");
      debug("assignments %O", assignments);

      yield put(actions.assign(assignments));
      break;

    case "ContractDefinition":
      let allocation = allocations[node.id];

      debug("Contract definition case");
      debug("allocations %O", allocations);
      debug("allocation %O", allocation);
      assignments = { byId: {} };
      for (let id in allocation.members) {
        id = Number(id); //not sure why we're getting them as strings, but...
        let idObj =
          address !== undefined
            ? { astId: id, address }
            : { astId: id, dummyAddress };
        let fullId = stableKeccak256(idObj);
        //we don't use makeAssignment here as we had to compute the ID anyway
        assignment = {
          ...idObj,
          id: fullId,
          ref: {
            ...((currentAssignments.byId[fullId] || {}).ref || {}),
            ...allocation.members[id].pointer
          }
        };
        assignments.byId[fullId] = assignment;
      }
      debug("assignments %O", assignments);

      yield put(actions.assign(assignments));
      break;

    case "VariableDeclaration":
      let varId = node.id;
      debug("Variable declaration case");
      debug("currentDepth %d varId %d", currentDepth, varId);

      //NOTE: We're going to make the assignment conditional here; here's why.
      //There's a bug where calling the autogenerated accessor for a public
      //contract variable causes the debugger to see two additional
      //declarations for that variable... which this code reads as local
      //variable declarations.  Rather than prevent this at the source, we're
      //just going to check for it here, by not adding a local variable if said
      //variable is already a contract variable.

      if (
        currentAssignments.byAstId[varId] !== undefined &&
        currentAssignments.byAstId[varId].some(
          id =>
            currentAssignments.byId[id].address !== undefined ||
            currentAssignments.byId[id].dummyAddress !== undefined
        )
      ) {
        break;
      }

      //otherwise, go ahead and make the assignment
      assignment = makeAssignment(
        { astId: varId, stackframe: currentDepth },
        {
          stack: {
            from: top - DecodeUtils.Definition.stackSize(node) + 1,
            to: top
          }
        }
      );
      assignments = { byId: { [assignment.id]: assignment } };
      yield put(actions.assign(assignments));
      break;

    case "IndexAccess":
      // to track `mapping` types known indices
      // (and also *some* known indices for arrays)

      debug("Index access case");

      //we're going to start by doing the same thing as in the default case
      //(see below) -- getting things ready for an assignment.  Then we're
      //going to forget this for a bit while we handle the rest...
      assignments = literalAssignments(node, stack, currentDepth);

      //we'll need this
      baseExpression = node.baseExpression;

      //but first, a diversion -- is this something that could not *possibly*
      //lead to a mapping?  i.e., either a bytes, or an array of non-reference
      //types, or a non-storage array?
      //if so, we'll just do the assign and quit out early
      //(note: we write it this way because mappings aren't caught by
      //isReference)
      if (
        DecodeUtils.Definition.typeClass(baseExpression) === "bytes" ||
        (DecodeUtils.Definition.typeClass(baseExpression) === "array" &&
          (DecodeUtils.Definition.isReference(node)
            ? DecodeUtils.Definition.referenceType(baseExpression) !== "storage"
            : !DecodeUtils.Definition.isMapping(node)))
      ) {
        debug("Index case bailed out early");
        debug("typeClass %s", DecodeUtils.Definition.typeClass(baseExpression));
        debug(
          "referenceType %s",
          DecodeUtils.Definition.referenceType(baseExpression)
        );
        debug("isReference(node) %o", DecodeUtils.Definition.isReference(node));
        yield put(actions.assign(assignments));
        break;
      }

      let keyDefinition = DecodeUtils.Definition.keyDefinition(
        baseExpression,
        scopes
      );
      //if we're dealing with an array, this will just hack up a uint definition
      //:)

      //begin subsection: key decoding
      //(I tried factoring this out into its own saga but it didn't work when I
      //did :P )
      yield put(actions.mapKeyDecoding(true));

      let indexValue;
      let indexDefinition = node.indexExpression;

      //why the loop? see the end of the block it heads for an explanatory
      //comment
      while (indexValue === undefined) {
        let indexId = indexDefinition.id;
        //indices need to be identified by stackframe
        let indexIdObj = { astId: indexId, stackframe: currentDepth };
        let fullIndexId = stableKeccak256(indexIdObj);

        const indexReference = (currentAssignments.byId[fullIndexId] || {}).ref;

        if (DecodeUtils.Definition.isSimpleConstant(indexDefinition)) {
          //while the main case is the next one, where we look for a prior
          //assignment, we need this case (and need it first) for two reasons:
          //1. some constant expressions (specifically, string and hex literals)
          //aren't sourcemapped to and so won't have a prior assignment
          //2. if the key type is bytesN but the expression is constant, the
          //value will go on the stack *left*-padded instead of right-padded,
          //so looking for a prior assignment will read the wrong value.
          //so instead it's preferable to use the constant directly.
          indexValue = yield call(decode, keyDefinition, {
            definition: indexDefinition
          });
        } else if (indexReference) {
          //if a prior assignment is found
          let splicedDefinition;
          //in general, we want to decode using the key definition, not the index
          //definition. however, the key definition may have the wrong location
          //on it.  so, when applicable, we splice the index definition location
          //onto the key definition location.
          if (DecodeUtils.Definition.isReference(indexDefinition)) {
            splicedDefinition = DecodeUtils.Definition.spliceLocation(
              keyDefinition,
              DecodeUtils.Definition.referenceType(indexDefinition)
            );
            //we could put code here to add on the "_ptr" ending when absent,
            //but we presently ignore that ending, so we'll skip that
          } else {
            splicedDefinition = keyDefinition;
          }
          indexValue = yield call(decode, splicedDefinition, indexReference);
        } else if (
          indexDefinition.referencedDeclaration &&
          scopes[indexDefinition.referenceDeclaration]
        ) {
          //there's one more reason we might have failed to decode it: it might be a
          //constant state variable.  Unfortunately, we don't know how to decode all
          //those at the moment, but we can handle the ones we do know how to decode.
          //In the future hopefully we will decode all of them
          debug(
            "referencedDeclaration %d",
            indexDefinition.referencedDeclaration
          );
          let indexConstantDeclaration =
            scopes[indexDefinition.referencedDeclaration].definition;
          debug("indexConstantDeclaration %O", indexConstantDeclaration);
          if (indexConstantDeclaration.constant) {
            let indexConstantDefinition = indexConstantDeclaration.value;
            //next line filters out constants we don't know how to handle
            if (
              DecodeUtils.Definition.isSimpleConstant(indexConstantDefinition)
            ) {
              indexValue = yield call(decode, keyDefinition, {
                definition: indexConstantDeclaration.value
              });
            }
          }
        }
        //there's still one more reason we might have failed to decode it:
        //certain (silent) type conversions aren't sourcemapped either.
        //(thankfully, any type conversion that actually *does* something seems
        //to be sourcemapped.)  So if we've failed to decode it, we try again
        //with the argument of the type conversion, if it is one; we leave
        //indexValue undefined so the loop will continue
        //(note that this case is last for a reason; if this were earlier, it
        //would catch *non*-silent type conversions, which we want to just read
        //off the stack)
        else if (indexDefinition.kind === "typeConversion") {
          indexDefinition = indexDefinition.arguments[0];
        }
        //otherwise, we've just totally failed to decode it, so we mark
        //indexValue as null (as distinct from undefined) to indicate this.  In
        //the future, we should be able to decode all mapping keys, but we're
        //not quite there yet, sorry (because we can't yet handle all constant
        //state variables)
        else {
          indexValue = null;
        }
        //now, as mentioned, retry in the typeConversion case
      }

      yield put(actions.mapKeyDecoding(false));
      //end subsection: key decoding

      debug("index value %O", indexValue);
      debug("keyDefinition %o", keyDefinition);

      //whew! But we're not done yet -- we need to turn this decoded key into
      //an actual path (assuming we *did* decode it)
      if (indexValue !== null) {
        path = fetchBasePath(
          baseExpression,
          mappedPaths,
          currentAssignments,
          currentDepth
        );

        let slot = { path };

        //we need to do things differently depending on whether we're dealing
        //with an array or mapping
        switch (DecodeUtils.Definition.typeClass(baseExpression)) {
          case "array":
            slot.hashPath = DecodeUtils.Definition.isDynamicArray(
              baseExpression
            );
            slot.offset = indexValue.muln(
              storageSize(node, referenceDeclarations, allocations).words
            );
            break;
          case "mapping":
            slot.key = indexValue;
            slot.keyEncoding = DecodeUtils.Definition.keyEncoding(
              keyDefinition
            );
            slot.offset = new BN(0);
            break;
          default:
            debug("unrecognized index access!");
        }
        debug("slot %O", slot);

        //now, map it! (and do the assign as well)
        yield put(
          actions.mapPathAndAssign(
            address || dummyAddress,
            slot,
            assignments,
            DecodeUtils.Definition.typeIdentifier(node),
            DecodeUtils.Definition.typeIdentifier(baseExpression)
          )
        );
      } else {
        //if we failed to decode, just do the assign from above
        debug("failed to decode, just assigning");
        yield put(actions.assign(assignments));
      }

      break;

    case "MemberAccess":
      //we're going to start by doing the same thing as in the default case
      //(see below) -- getting things ready for an assignment.  Then we're
      //going to forget this for a bit while we handle the rest...
      assignments = literalAssignments(node, stack, currentDepth);

      debug("Member access case");

      //MemberAccess uses expression, not baseExpression
      baseExpression = node.expression;

      //if this isn't a storage struct, or the element isn't of reference type,
      //we'll just do the assignment and quit out (again, note that mappings
      //aren't caught by isReference)
      if (
        DecodeUtils.Definition.typeClass(baseExpression) !== "struct" ||
        (DecodeUtils.Definition.isReference(node)
          ? DecodeUtils.Definition.referenceType(baseExpression) !== "storage"
          : !DecodeUtils.Definition.isMapping(node))
      ) {
        debug("Member case bailed out early");
        yield put(actions.assign(assignments));
        break;
      }

      //but if it is a storage struct, we have to map the path as well
      path = fetchBasePath(
        baseExpression,
        mappedPaths,
        currentAssignments,
        currentDepth
      );

      slot = { path };

      let structId = DecodeUtils.Definition.typeId(baseExpression);
      let memberAllocation =
        allocations[structId].members[node.referencedDeclaration];

      slot.offset = memberAllocation.pointer.storage.from.slot.offset.clone();

      debug("slot %o", slot);
      yield put(
        actions.mapPathAndAssign(
          address || dummyAddress,
          slot,
          assignments,
          DecodeUtils.Definition.typeIdentifier(node),
          DecodeUtils.Definition.typeIdentifier(baseExpression)
        )
      );

    default:
      if (node.typeDescriptions == undefined) {
        break;
      }

      debug("decoding expression value %O", node.typeDescriptions);
      debug("default case");
      debug("currentDepth %d node.id %d", currentDepth, node.id);

      assignments = literalAssignments(node, stack, currentDepth);
      yield put(actions.assign(assignments));
      break;
  }
}

export function* reset() {
  yield put(actions.reset());
}

export function* learnAddressSaga(dummyAddress, address) {
  debug("about to learn an address");
  yield put(actions.learnAddress(dummyAddress, address));
  debug("address learnt");
}

export function* recordAllocations() {
  let contracts = yield select(data.views.userDefinedTypes.contractDefinitions);
  debug("contracts %O", contracts);
  let referenceDeclarations = yield select(data.views.referenceDeclarations);
  debug("referenceDeclarations %O", referenceDeclarations);
  let storageAllocations = getStorageAllocations(
    referenceDeclarations,
    contracts
  );
  debug("storageAllocations %O", storageAllocations);
  yield put(actions.allocate(storageAllocations));
}

function makeAssignment(idObj, ref) {
  let id = stableKeccak256(idObj);
  return { ...idObj, id, ref };
}

function literalAssignments(node, stack, currentDepth) {
  let top = stack.length - 1;

  let literal = readStack(
    stack,
    top - DecodeUtils.Definition.stackSize(node) + 1,
    top
  );

  let assignment = makeAssignment(
    { astId: node.id, stackframe: currentDepth },
    { literal }
  );

  return { byId: { [assignment.id]: assignment } };
}

function fetchBasePath(
  baseNode,
  mappedPaths,
  currentAssignments,
  currentDepth
) {
  let fullId = stableKeccak256({
    astId: baseNode.id,
    stackframe: currentDepth
  });
  let basePathRef = mappedPaths.byId[fullId];
  //may be undefined! this means that the base is not an index or member
  //access, but just an ordinary mapping or array or struct.  note that it may
  //be via a pointer! for this reason, we fetch the base from the stack rather
  //than from the allocation table
  if (basePathRef !== undefined) {
    return mappedPaths.byAddress[basePathRef.address].byType[
      basePathRef.typeIdentifier
    ].bySlotAddress[basePathRef.slotAddress];
  } else {
    //base expression is an expression, and so has a literal assigned to
    //it
    let offset = DecodeUtils.Conversion.toBN(
      currentAssignments.byId[fullId].ref.literal
    );
    return { offset };
  }
}

export function* saga() {
  yield takeEvery(TICK, tickSaga);
}

export default prefixName("data", saga);
<commit_msg>Remove needless putResolves (in favor of put).<commit_after>import debugModule from "debug";
const debug = debugModule("debugger:data:sagas");

import { put, takeEvery, select, call } from "redux-saga/effects";

import { prefixName, stableKeccak256 } from "lib/helpers";

import { TICK } from "lib/trace/actions";
import * as actions from "../actions";
import * as trace from "lib/trace/sagas";

import data from "../selectors";

import * as DecodeUtils from "truffle-decode-utils";
import { getStorageAllocations, readStack, storageSize } from "truffle-decoder";
import BN from "bn.js";

export function* scope(nodeId, pointer, parentId, sourceId) {
  yield put(actions.scope(nodeId, pointer, parentId, sourceId));
}

export function* declare(node) {
  yield put(actions.declare(node));
}

export function* defineType(node) {
  yield put(actions.defineType(node));
}

function* tickSaga() {
  debug("got TICK");

  yield* variablesAndMappingsSaga();
  yield* trace.signalTickSagaCompletion();
}

function* variablesAndMappingsSaga() {
  let node = (yield select(data.views.ast)).node;
  let decode = yield select(data.views.decoder);
  let scopes = yield select(data.views.scopes.inlined);
  let referenceDeclarations = yield select(data.views.referenceDeclarations);
  let allocations = yield select(data.info.allocations.storage);
  let currentAssignments = yield select(data.proc.assignments);
  let mappedPaths = yield select(data.proc.mappedPaths);
  let currentDepth = yield select(data.current.functionDepth);
  let address = yield select(data.current.address); //may be undefined
  let dummyAddress = yield select(data.current.dummyAddress);

  let stack = yield select(data.next.state.stack);
  if (!stack) {
    return;
  }

  let top = stack.length - 1;
  var assignment, assignments, baseExpression, slot, path;

  if (!node) {
    return;
  }

  // stack is only ready for interpretation after the last step of each
  // source range
  //
  // the data module always looks at the result of a particular opcode
  // (i.e., the following trace step's stack/memory/storage), so this
  // asserts that the _current_ operation is the final one before
  // proceeding
  if (!(yield select(data.views.atLastInstructionForSourceRange))) {
    return;
  }

  switch (node.nodeType) {
    case "FunctionDefinition":
      let parameters = node.parameters.parameters;
      //note that we do *not* include return parameters, since those are
      //handled by the VariableDeclaration case (no, I don't know why it
      //works out that way)
      let reverseParameters = parameters.slice().reverse();
      //reverse is in-place, so we use slice() to clone first
      debug("reverseParameters %o", parameters);

      let currentPosition = top;
      assignments = { byId: {} };

      for (let parameter of reverseParameters) {
        let words = DecodeUtils.Definition.stackSize(parameter);
        let pointer = {
          stack: {
            from: currentPosition - words + 1,
            to: currentPosition
          }
        };
        let assignment = makeAssignment(
          { astId: parameter.id, stackframe: currentDepth },
          pointer
        );
        assignments.byId[assignment.id] = assignment;
        currentPosition -= words;
      }

      debug("Function definition case");
      debug("assignments %O", assignments);

      yield put(actions.assign(assignments));
      break;

    case "ContractDefinition":
      let allocation = allocations[node.id];

      debug("Contract definition case");
      debug("allocations %O", allocations);
      debug("allocation %O", allocation);
      assignments = { byId: {} };
      for (let id in allocation.members) {
        id = Number(id); //not sure why we're getting them as strings, but...
        let idObj =
          address !== undefined
            ? { astId: id, address }
            : { astId: id, dummyAddress };
        let fullId = stableKeccak256(idObj);
        //we don't use makeAssignment here as we had to compute the ID anyway
        assignment = {
          ...idObj,
          id: fullId,
          ref: {
            ...((currentAssignments.byId[fullId] || {}).ref || {}),
            ...allocation.members[id].pointer
          }
        };
        assignments.byId[fullId] = assignment;
      }
      debug("assignments %O", assignments);

      yield put(actions.assign(assignments));
      break;

    case "VariableDeclaration":
      let varId = node.id;
      debug("Variable declaration case");
      debug("currentDepth %d varId %d", currentDepth, varId);

      //NOTE: We're going to make the assignment conditional here; here's why.
      //There's a bug where calling the autogenerated accessor for a public
      //contract variable causes the debugger to see two additional
      //declarations for that variable... which this code reads as local
      //variable declarations.  Rather than prevent this at the source, we're
      //just going to check for it here, by not adding a local variable if said
      //variable is already a contract variable.

      if (
        currentAssignments.byAstId[varId] !== undefined &&
        currentAssignments.byAstId[varId].some(
          id =>
            currentAssignments.byId[id].address !== undefined ||
            currentAssignments.byId[id].dummyAddress !== undefined
        )
      ) {
        break;
      }

      //otherwise, go ahead and make the assignment
      assignment = makeAssignment(
        { astId: varId, stackframe: currentDepth },
        {
          stack: {
            from: top - DecodeUtils.Definition.stackSize(node) + 1,
            to: top
          }
        }
      );
      assignments = { byId: { [assignment.id]: assignment } };
      yield put(actions.assign(assignments));
      break;

    case "IndexAccess":
      // to track `mapping` types known indices
      // (and also *some* known indices for arrays)

      debug("Index access case");

      //we're going to start by doing the same thing as in the default case
      //(see below) -- getting things ready for an assignment.  Then we're
      //going to forget this for a bit while we handle the rest...
      assignments = literalAssignments(node, stack, currentDepth);

      //we'll need this
      baseExpression = node.baseExpression;

      //but first, a diversion -- is this something that could not *possibly*
      //lead to a mapping?  i.e., either a bytes, or an array of non-reference
      //types, or a non-storage array?
      //if so, we'll just do the assign and quit out early
      //(note: we write it this way because mappings aren't caught by
      //isReference)
      if (
        DecodeUtils.Definition.typeClass(baseExpression) === "bytes" ||
        (DecodeUtils.Definition.typeClass(baseExpression) === "array" &&
          (DecodeUtils.Definition.isReference(node)
            ? DecodeUtils.Definition.referenceType(baseExpression) !== "storage"
            : !DecodeUtils.Definition.isMapping(node)))
      ) {
        debug("Index case bailed out early");
        debug("typeClass %s", DecodeUtils.Definition.typeClass(baseExpression));
        debug(
          "referenceType %s",
          DecodeUtils.Definition.referenceType(baseExpression)
        );
        debug("isReference(node) %o", DecodeUtils.Definition.isReference(node));
        yield put(actions.assign(assignments));
        break;
      }

      let keyDefinition = DecodeUtils.Definition.keyDefinition(
        baseExpression,
        scopes
      );
      //if we're dealing with an array, this will just hack up a uint definition
      //:)

      //begin subsection: key decoding
      //(I tried factoring this out into its own saga but it didn't work when I
      //did :P )
      yield put(actions.mapKeyDecoding(true));

      let indexValue;
      let indexDefinition = node.indexExpression;

      //why the loop? see the end of the block it heads for an explanatory
      //comment
      while (indexValue === undefined) {
        let indexId = indexDefinition.id;
        //indices need to be identified by stackframe
        let indexIdObj = { astId: indexId, stackframe: currentDepth };
        let fullIndexId = stableKeccak256(indexIdObj);

        const indexReference = (currentAssignments.byId[fullIndexId] || {}).ref;

        if (DecodeUtils.Definition.isSimpleConstant(indexDefinition)) {
          //while the main case is the next one, where we look for a prior
          //assignment, we need this case (and need it first) for two reasons:
          //1. some constant expressions (specifically, string and hex literals)
          //aren't sourcemapped to and so won't have a prior assignment
          //2. if the key type is bytesN but the expression is constant, the
          //value will go on the stack *left*-padded instead of right-padded,
          //so looking for a prior assignment will read the wrong value.
          //so instead it's preferable to use the constant directly.
          indexValue = yield call(decode, keyDefinition, {
            definition: indexDefinition
          });
        } else if (indexReference) {
          //if a prior assignment is found
          let splicedDefinition;
          //in general, we want to decode using the key definition, not the index
          //definition. however, the key definition may have the wrong location
          //on it.  so, when applicable, we splice the index definition location
          //onto the key definition location.
          if (DecodeUtils.Definition.isReference(indexDefinition)) {
            splicedDefinition = DecodeUtils.Definition.spliceLocation(
              keyDefinition,
              DecodeUtils.Definition.referenceType(indexDefinition)
            );
            //we could put code here to add on the "_ptr" ending when absent,
            //but we presently ignore that ending, so we'll skip that
          } else {
            splicedDefinition = keyDefinition;
          }
          indexValue = yield call(decode, splicedDefinition, indexReference);
        } else if (
          indexDefinition.referencedDeclaration &&
          scopes[indexDefinition.referenceDeclaration]
        ) {
          //there's one more reason we might have failed to decode it: it might be a
          //constant state variable.  Unfortunately, we don't know how to decode all
          //those at the moment, but we can handle the ones we do know how to decode.
          //In the future hopefully we will decode all of them
          debug(
            "referencedDeclaration %d",
            indexDefinition.referencedDeclaration
          );
          let indexConstantDeclaration =
            scopes[indexDefinition.referencedDeclaration].definition;
          debug("indexConstantDeclaration %O", indexConstantDeclaration);
          if (indexConstantDeclaration.constant) {
            let indexConstantDefinition = indexConstantDeclaration.value;
            //next line filters out constants we don't know how to handle
            if (
              DecodeUtils.Definition.isSimpleConstant(indexConstantDefinition)
            ) {
              indexValue = yield call(decode, keyDefinition, {
                definition: indexConstantDeclaration.value
              });
            }
          }
        }
        //there's still one more reason we might have failed to decode it:
        //certain (silent) type conversions aren't sourcemapped either.
        //(thankfully, any type conversion that actually *does* something seems
        //to be sourcemapped.)  So if we've failed to decode it, we try again
        //with the argument of the type conversion, if it is one; we leave
        //indexValue undefined so the loop will continue
        //(note that this case is last for a reason; if this were earlier, it
        //would catch *non*-silent type conversions, which we want to just read
        //off the stack)
        else if (indexDefinition.kind === "typeConversion") {
          indexDefinition = indexDefinition.arguments[0];
        }
        //otherwise, we've just totally failed to decode it, so we mark
        //indexValue as null (as distinct from undefined) to indicate this.  In
        //the future, we should be able to decode all mapping keys, but we're
        //not quite there yet, sorry (because we can't yet handle all constant
        //state variables)
        else {
          indexValue = null;
        }
        //now, as mentioned, retry in the typeConversion case
      }

      yield put(actions.mapKeyDecoding(false));
      //end subsection: key decoding

      debug("index value %O", indexValue);
      debug("keyDefinition %o", keyDefinition);

      //whew! But we're not done yet -- we need to turn this decoded key into
      //an actual path (assuming we *did* decode it)
      if (indexValue !== null) {
        path = fetchBasePath(
          baseExpression,
          mappedPaths,
          currentAssignments,
          currentDepth
        );

        let slot = { path };

        //we need to do things differently depending on whether we're dealing
        //with an array or mapping
        switch (DecodeUtils.Definition.typeClass(baseExpression)) {
          case "array":
            slot.hashPath = DecodeUtils.Definition.isDynamicArray(
              baseExpression
            );
            slot.offset = indexValue.muln(
              storageSize(node, referenceDeclarations, allocations).words
            );
            break;
          case "mapping":
            slot.key = indexValue;
            slot.keyEncoding = DecodeUtils.Definition.keyEncoding(
              keyDefinition
            );
            slot.offset = new BN(0);
            break;
          default:
            debug("unrecognized index access!");
        }
        debug("slot %O", slot);

        //now, map it! (and do the assign as well)
        yield put(
          actions.mapPathAndAssign(
            address || dummyAddress,
            slot,
            assignments,
            DecodeUtils.Definition.typeIdentifier(node),
            DecodeUtils.Definition.typeIdentifier(baseExpression)
          )
        );
      } else {
        //if we failed to decode, just do the assign from above
        debug("failed to decode, just assigning");
        yield put(actions.assign(assignments));
      }

      break;

    case "MemberAccess":
      //we're going to start by doing the same thing as in the default case
      //(see below) -- getting things ready for an assignment.  Then we're
      //going to forget this for a bit while we handle the rest...
      assignments = literalAssignments(node, stack, currentDepth);

      debug("Member access case");

      //MemberAccess uses expression, not baseExpression
      baseExpression = node.expression;

      //if this isn't a storage struct, or the element isn't of reference type,
      //we'll just do the assignment and quit out (again, note that mappings
      //aren't caught by isReference)
      if (
        DecodeUtils.Definition.typeClass(baseExpression) !== "struct" ||
        (DecodeUtils.Definition.isReference(node)
          ? DecodeUtils.Definition.referenceType(baseExpression) !== "storage"
          : !DecodeUtils.Definition.isMapping(node))
      ) {
        debug("Member case bailed out early");
        yield put(actions.assign(assignments));
        break;
      }

      //but if it is a storage struct, we have to map the path as well
      path = fetchBasePath(
        baseExpression,
        mappedPaths,
        currentAssignments,
        currentDepth
      );

      slot = { path };

      let structId = DecodeUtils.Definition.typeId(baseExpression);
      let memberAllocation =
        allocations[structId].members[node.referencedDeclaration];

      slot.offset = memberAllocation.pointer.storage.from.slot.offset.clone();

      debug("slot %o", slot);
      yield put(
        actions.mapPathAndAssign(
          address || dummyAddress,
          slot,
          assignments,
          DecodeUtils.Definition.typeIdentifier(node),
          DecodeUtils.Definition.typeIdentifier(baseExpression)
        )
      );

    default:
      if (node.typeDescriptions == undefined) {
        break;
      }

      debug("decoding expression value %O", node.typeDescriptions);
      debug("default case");
      debug("currentDepth %d node.id %d", currentDepth, node.id);

      assignments = literalAssignments(node, stack, currentDepth);
      yield put(actions.assign(assignments));
      break;
  }
}

export function* reset() {
  yield put(actions.reset());
}

export function* learnAddressSaga(dummyAddress, address) {
  debug("about to learn an address");
  yield put(actions.learnAddress(dummyAddress, address));
  debug("address learnt");
}

export function* recordAllocations() {
  let contracts = yield select(data.views.userDefinedTypes.contractDefinitions);
  debug("contracts %O", contracts);
  let referenceDeclarations = yield select(data.views.referenceDeclarations);
  debug("referenceDeclarations %O", referenceDeclarations);
  let storageAllocations = getStorageAllocations(
    referenceDeclarations,
    contracts
  );
  debug("storageAllocations %O", storageAllocations);
  yield put(actions.allocate(storageAllocations));
}

function makeAssignment(idObj, ref) {
  let id = stableKeccak256(idObj);
  return { ...idObj, id, ref };
}

function literalAssignments(node, stack, currentDepth) {
  let top = stack.length - 1;

  let literal = readStack(
    stack,
    top - DecodeUtils.Definition.stackSize(node) + 1,
    top
  );

  let assignment = makeAssignment(
    { astId: node.id, stackframe: currentDepth },
    { literal }
  );

  return { byId: { [assignment.id]: assignment } };
}

function fetchBasePath(
  baseNode,
  mappedPaths,
  currentAssignments,
  currentDepth
) {
  let fullId = stableKeccak256({
    astId: baseNode.id,
    stackframe: currentDepth
  });
  let basePathRef = mappedPaths.byId[fullId];
  //may be undefined! this means that the base is not an index or member
  //access, but just an ordinary mapping or array or struct.  note that it may
  //be via a pointer! for this reason, we fetch the base from the stack rather
  //than from the allocation table
  if (basePathRef !== undefined) {
    return mappedPaths.byAddress[basePathRef.address].byType[
      basePathRef.typeIdentifier
    ].bySlotAddress[basePathRef.slotAddress];
  } else {
    //base expression is an expression, and so has a literal assigned to
    //it
    let offset = DecodeUtils.Conversion.toBN(
      currentAssignments.byId[fullId].ref.literal
    );
    return { offset };
  }
}

export function* saga() {
  yield takeEvery(TICK, tickSaga);
}

export default prefixName("data", saga);

{'fraction_non_alphanumeric': 0.09538018862897639, 'fraction_numerical': 0.0012788405179304098, 'mean_word_length': 2.4700009244707406, 'pattern_counts': {'":': 14, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2760935', 'n_tokens_mistral': 9884, 'n_tokens_neox': 9611, 'n_words': 3253}