`payable` typecast has incorrect byte length in AST
Description
As stated in the title. Reproducible even with the latest solc version (0.8.30).
byte_length should be 7 instead of 8.
node_type='ElementaryTypeNameExpression' src=Src(byte_offset=1494, byte_length=8, file_id=7) id=504 is_constant=False
is_l_value=False is_pure=True l_value_requested=False
type_descriptions=TypeDescriptionsModel(type_identifier='t_type$_t_address_payable_$', type_string='type(address payable)')
type_name=SolcElementaryTypeName(node_type='ElementaryTypeName', src=Src(byte_offset=1494, byte_length=8, file_id=7),
id=503, type_descriptions=TypeDescriptionsModel(type_identifier=None, type_string=None), name='address',
state_mutability=<StateMutability.PAYABLE: 'payable'>) argument_types=[TypeDescriptionsModel(type_identifier='t_address',
type_string='address')]
Environment
- Compiler version: 0.8.30
- Compilation pipeline (legacy, IR, EOF): legacy
- Target EVM version (as per compiler settings): default
- Framework/IDE (e.g. Foundry, Hardhat, Remix):
- EVM execution environment / backend / blockchain client:
- Operating system:
Steps to Reproduce
Reproducible in Safe, for example.
hey @michprev i would like to work on this bug.
Hey @michprev i was looking for this part of code which you mention in the description for debugging but couldn't find it in the codebase. So could you please guide me where it is so that i could debug it , thankyou .
This is how to reproduce the issue:
contract C {constructor() {payable(0);}}
Then you can run solc --ast-compact-json which you will get or just:
echo "contract C {constructor() {payable(0);}}" | solc --ast-compact-json -
which will give you:
json
{
"absolutePath": "<stdin>",
"exportedSymbols": { "C": [10] },
"id": 11,
"nodeType": "SourceUnit",
"nodes": [
{
"abstract": false,
"baseContracts": [],
"canonicalName": "C",
"contractDependencies": [],
"contractKind": "contract",
"fullyImplemented": true,
"id": 10,
"linearizedBaseContracts": [10],
"name": "C",
"nameLocation": "9:1:0",
"nodeType": "ContractDefinition",
"nodes": [
{
"body": {
"id": 8,
"nodeType": "Block",
"src": "26:13:0",
"statements": [
{
"expression": {
"arguments": [
{
"hexValue": "30",
"id": 5,
"isConstant": false,
"isLValue": false,
"isPure": true,
"kind": "number",
"lValueRequested": false,
"nodeType": "Literal",
"src": "35:1:0",
"typeDescriptions": {
"typeIdentifier": "t_rational_0_by_1",
"typeString": "int_const 0"
},
"value": "0"
}
],
"expression": {
"argumentTypes": [
{
"typeIdentifier": "t_rational_0_by_1",
"typeString": "int_const 0"
}
],
"id": 4,
"isConstant": false,
"isLValue": false,
"isPure": true,
"lValueRequested": false,
"nodeType": "ElementaryTypeNameExpression",
"src": "27:8:0",
"typeDescriptions": {
"typeIdentifier": "t_type$_t_address_payable_$",
"typeString": "type(address payable)"
},
"typeName": {
"id": 3,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "27:8:0",
"stateMutability": "payable",
"typeDescriptions": {}
}
},
"id": 6,
"isConstant": false,
"isLValue": false,
"isPure": true,
"kind": "typeConversion",
"lValueRequested": false,
"nameLocations": [],
"names": [],
"nodeType": "FunctionCall",
"src": "27:10:0",
"tryCall": false,
"typeDescriptions": {
"typeIdentifier": "t_address_payable",
"typeString": "address payable"
}
},
"id": 7,
"nodeType": "ExpressionStatement",
"src": "27:10:0"
}
]
},
"id": 9,
"implemented": true,
"kind": "constructor",
"modifiers": [],
"name": "",
"nameLocation": "-1:-1:-1",
"nodeType": "FunctionDefinition",
"parameters": {
"id": 1,
"nodeType": "ParameterList",
"parameters": [],
"src": "23:2:0"
},
"returnParameters": {
"id": 2,
"nodeType": "ParameterList",
"parameters": [],
"src": "26:0:0"
},
"scope": 10,
"src": "12:27:0",
"stateMutability": "nonpayable",
"virtual": false,
"visibility": "public"
}
],
"scope": 11,
"src": "0:40:0",
"usedErrors": [],
"usedEvents": []
}
],
"src": "0:41:0"
}
and the important part being:
"id": 4,
"isConstant": false,
"isLValue": false,
"isPure": true,
"lValueRequested": false,
"nodeType": "ElementaryTypeNameExpression",
"src": "27:8:0",
"typeDescriptions": {
"typeIdentifier": "t_type$_t_address_payable_$",
"typeString": "type(address payable)"
},
"typeName": {
"id": 3,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "27:8:0",
"stateMutability": "payable",
"typeDescriptions": {}
}
Note the "src": "27:8:0" for payable token which means it starts at 27 with length 8 in the 0 source unit.
This issue stems from:
https://github.com/ethereum/solidity/blob/584cdcc5d994778ae141fd82e5e616bbed36d781/libsolidity/parsing/Parser.cpp#L2268-L2278
expectToken(Token::Payable) performs:
https://github.com/ethereum/solidity/blob/584cdcc5d994778ae141fd82e5e616bbed36d781/liblangutil/ParserBase.cpp#L73-L83
which calls advance() and thus advancing the current position by at least one and so when nodeFactory.markEndPosition() is called, the end is marked by at least one more character.
This was introduced in:
- https://github.com/ethereum/solidity/pull/7349
Possibly following the same pattern as its previous if else block for Token::New (introduced in https://github.com/ethereum/solidity/commit/6f621f8486ca6685b29008cb66ea6a819d7c91dc ):
https://github.com/ethereum/solidity/blob/584cdcc5d994778ae141fd82e5e616bbed36d781/libsolidity/parsing/Parser.cpp#L2261-L2267
which probably also has the same issue. I think these initial expectToken calls should happen after the calls to nodeFactory.createNode. So like:
else if (m_scanner->currentToken() == Token::Payable)
{
nodeFactory.markEndPosition();
auto expressionType = nodeFactory.createNode<ElementaryTypeName>(
ElementaryTypeNameToken(Token::Address, 0, 0),
std::make_optional(StateMutability::Payable)
);
expression = nodeFactory.createNode<ElementaryTypeNameExpression>(expressionType);
expectToken(Token::Payable); // moved down here
expectToken(Token::LParen, false);
}
@leonardoalt does that make sense?