Error al compilar: pila demasiado profunda

Al intentar compilar, me sale el siguiente error:

"Error interno del compilador: pila demasiado profunda, intente eliminar las variables locales".

¿Hay alguna forma de evitar esto? No estoy seguro de si podré eliminar suficientes variables para solucionar esto.

¡Gracias!

Respuestas (6)

Estás golpeando una StackTooDeepException .

El código de Solidity no parece ser consistente en la cantidad de variables que ve como un problema, pero tiene un límite de alrededor de 16 o 17. (Aunque claramente el límite inferior de 16 será el que se active. ..)

CommonSubexpressionEliminator.cpp y CompilerUtils.cpp :

assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep, try removing local variables.");

ContractCompiler.cpp :

solAssert(stackLayout.size() <= 17, "Stack too deep, try removing local variables.");

Sin ver su código, es difícil comentar más sobre las posibles soluciones, pero una cosa que puede intentar sería dividir las funciones más grandes en otras más pequeñas.


Editar 2019:

Una explicación muy detallada de este error, y cómo se puede evitar, se analiza en el artículo "Stack Too Deep" - Error en Solidity .

¡Gracias por la sugerencia! ¿Contaría una matriz como 1 variable local?
Yo creo que sí, sí. De lo contrario, los tamaños de matriz también estarían limitados a 16, que no es una matriz particularmente grande.
No estoy tratando de revivir esto, pero tuve que preguntar ya que también recibo este error. ¿Es el número máximo de variables locales menos de 10 ahora? Tengo este error con 15 variables.
Hola, @MedMansour: detecté tu otra pregunta sobre esto. ¿Cuántos tienes que quitar antes de que desaparezca el error?
No obtuve ningún error al dejar alrededor de 8 o 9 variables de mi estructura, pero ya cambié a pasar una matriz de tamaño fijo o argumentos separados. sin embargo, no sé qué significa variable local. ¿Son solo las variables en los argumentos? o los que están en returns()declaración o la suma de aquellos con las variables locales dentro de la función.
Estoy devolviendo solo 8 variables a través de una estructura y ya recibo este error.return ( myStruct[id].var1, myStruct[id].var2, ...);
El límite estricto se trata realmente de la cantidad de ranuras de pila, no de las variables: la pila EVM tiene 1024 ranuras, pero solo puede acceder a las 16/17 principales en un momento dado. En la mayoría de los casos, 1 variable requiere 1 ranura, pero no siempre. Por ejemplo, las matrices dinámicas de datos de llamadas y los punteros de función externos necesitan 2 (esto está determinado por makeStackItems()la fuente del compilador). Además, algunas ranuras adicionales pueden usarse internamente: el lenguaje oculta la pila a propósito y el diseño exacto de la pila debe considerarse un detalle de implementación.

Uniswap parece haber encontrado una buena solución para este problema. Rodea una parte de tu función con corchetes:

{ // scope for _token{0,1}, avoids stack too deep errors
  address _token0 = token0;
  address _token1 = token1;
  require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
  if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
  if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
  if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
  balance0 = IERC20(_token0).balanceOf(address(this));
  balance1 = IERC20(_token1).balanceOf(address(this));
}

Eche un vistazo a UniswapV2.sol para ver el contexto completo.

¿Qué cambia eso, crees?
No estoy seguro. Lo vi en el código de producción de Uniswap v2, así que pensé que debía ser bueno.
El problema de la pila surge con mayor frecuencia cuando hay demasiadas variables/parámetros locales en el alcance actual. Si coloca algunas declaraciones de variables en un bloque, quedan fuera del alcance cuando finaliza el bloque, lo que reduce la cantidad de variables que deben mantenerse en la pila después de él. Sin embargo, en última instancia, el seguimiento de la vida útil de las variables locales en una función es algo que el compilador/optimizador debería poder descifrar sin tales "trucos", por lo que incluso si esto todavía ayuda con el generador de código actual, el próximo generador de código basado en Yul debería ser mucho más inteligente al respecto.

https://github.com/ethereum/solidity/issues/267

Depende de cuán complejas sean las expresiones dentro de la función, pero más de 16 variables locales no funcionarán. Sin embargo, esta historia debería solucionarlo: https://www.pivotaltracker.com/n/projects/1189488/stories/99085498

La historia no se ha iniciado.

Parece que ya está completo.
@updogliu o quien conozca las últimas noticias, publique una respuesta.
Tiendo a estar de acuerdo con @eth. Esto no parece estar resuelto en 0.4.18.

Una solución para esto es colocar las variables locales en una matriz del mismo tipo que en las matrices EVM que solo ocupan una ranura de pila. Por lo tanto, las funciones pueden hacerse mucho más grandes antes de alcanzar el límite de 16 ranuras.

Por ejemplo:

contract A {

    // This will get the error: 'stack too deep, try...'
    function deepStack
    (
        uint8 _a,
        uint8 _b,
        uint8 _c,
        uint8 _d,
        uint16 _e,
        uint16 _f,
        uint16 _g,
        uint16 _h,
        uint32 _i,
        uint32 _j,
        uint32 _k,
        uint32 _l,
        uint64 _m,
        uint64 _n,
        uint64 _o,
        uint64 _p,
        uint128 _q
    )
        public
        returns (bool success)
    {
        return true;
    }

    // This function works
    function deepStackSolution
    (
        uint8[] _aToD,
        uint16[] _eToH,
        uint32[] _iToL,
        uint64[] _mToP,
        uint128 _q
    )
        public
        returns (bool success)
    {
        return true;
    }

}

Sin embargo, tenga en cuenta que la capacidad de almacenamiento de cada ranura sigue siendo limitada. De esta manera, el almacenamiento proporcionado se utilizará de manera más eficiente. Si está utilizando números realmente grandes, esto también podría alcanzar sus límites pronto.

Aquí hay algo divertido. Simplemente cambié un método de publica externaly recibí el mensaje:

/Users/cliff/Documents/in-app-pro-shop/contracts/SKUFactory.sol:42:23: 
CompilerError: Stack too deep, try removing local variables.
skus.push(SKU(_shopId, skuId, _skuTypeId, _price, _name, _desc, _consumable, _limited, _limit));
              ^-----^

¡ Cambiarlo de nuevo a publicelimina el error! Aparentemente, es un poco más matizado que cuántas variables locales hay. Aquí está la función:

/**
 * @notice Create a SKU (Shopkeeping Unit) for a Shop
 * @dev Can only be run by shop owner
 */
function createSKU(
    uint256 _shopId,
    uint256 _skuTypeId,
    uint256 _price,
    string _name,
    string _desc,
    bool _consumable,
    bool _limited,
    uint256 _limit
)
    public
    onlyShopOwner(_shopId)
    returns(uint256)
{
    // SKUs must have a non-zero price
    require(_price > 0);

    // Get SKU ID
    uint256 skuId = skus.length;

    // Create and store SKU Type
    skus.push(SKU(_shopId, skuId, _skuTypeId, _price, _name, _desc, _consumable, _limited, _limit));

    // Add SKU to Shop's SKU list
    shopSKUs[_shopId].push(skuId);

    // Add SKU ID to SKU Type's SKU list
    skuTypeSKUs[_skuTypeId].push(skuId);

    // Emit Event with name of the new SKU
    emit NewSKU(_shopId, skuId, _name);

    // Return the new SKU ID
    return skuId;
}

Estaba haciendo el cambio en respuesta a esta discusión sobre las mejores prácticas para externalvs public, donde se explica (aunque un poco turbiamente) cómo las funciones se manejan de manera diferente en esos casos. Supongo que esa es la raíz de por qué no hay un número específico de variables locales que desencadenan este error.

La causa raíz real es que se trata de ranuras de pila y las variables no necesariamente se asignan a las ranuras 1:1. Creo que la razón por la que recibió el error es que los parámetros de cadena de calldata requieren dos ranuras, mientras que las cadenas de memoria solo toman una. Elegir externalen lugar de publicforzar probablemente que los parámetros estén en calldata.

Simplemente empaque sus variables en una matriz de memoria. Por ejemplo, supongamos que tiene 30 variables de direcciones locales. En vez de:

address addr1 = 0x0000000000000000000000000000000000000001;
address addr2 = 0x0000000000000000000000000000000000000002;
address addr3 = 0x0000000000000000000000000000000000000003;
// etc

Tu puedes hacer:

address[] memory joinedAddresses = new address[](30);
joinedAddresses[0] = 0x0000000000000000000000000000000000000001;
joinedAddresses[1] = 0x0000000000000000000000000000000000000002;
joinedAddresses[2] = 0x0000000000000000000000000000000000000003;
// etc

Nota: probé este método en Solidity 0.8.4.