From fb7c969ce89912b80657b5bce3cd17edd81468bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 25 Mar 2022 20:03:26 +0100 Subject: [PATCH 01/21] Tests for assembly instructions allowed with mutable/view/pure functions --- .../inline_assembly_instructions_allowed.sol | 92 +++++++++++++++++++ ...ine_assembly_instructions_allowed_pure.sol | 90 ++++++++++++++++++ ...ine_assembly_instructions_allowed_view.sol | 91 ++++++++++++++++++ ...nline_assembly_instructions_disallowed.sol | 28 ++++++ ..._assembly_instructions_disallowed_pure.sol | 80 ++++++++++++++++ ..._assembly_instructions_disallowed_view.sol | 38 ++++++++ 6 files changed, 419 insertions(+) create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_view.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_pure.sol create mode 100644 test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_view.sol diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed.sol new file mode 100644 index 000000000..8b634c219 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed.sol @@ -0,0 +1,92 @@ +contract C { + function f() public { + assembly { + stop() + pop(add(0, 1)) + pop(sub(0, 1)) + pop(mul(0, 1)) + pop(div(0, 1)) + pop(sdiv(0, 1)) + pop(mod(0, 1)) + pop(smod(0, 1)) + pop(exp(0, 1)) + pop(not(0)) + pop(lt(0, 1)) + pop(gt(0, 1)) + pop(slt(0, 1)) + pop(sgt(0, 1)) + pop(eq(0, 1)) + pop(iszero(0)) + pop(and(0, 1)) + pop(or(0, 1)) + pop(xor(0, 1)) + pop(byte(0, 1)) + pop(shl(0, 1)) + pop(shr(0, 1)) + pop(sar(0, 1)) + pop(addmod(0, 1, 2)) + pop(mulmod(0, 1, 2)) + pop(signextend(0, 1)) + pop(keccak256(0, 1)) + pop(0) + pop(mload(0)) + mstore(0, 1) + mstore8(0, 1) + pop(sload(0)) + sstore(0, 1) + pop(gas()) + pop(address()) + pop(balance(0)) + pop(selfbalance()) + pop(caller()) + pop(callvalue()) + pop(calldataload(0)) + pop(calldatasize()) + calldatacopy(0, 1, 2) + pop(codesize()) + codecopy(0, 1, 2) + pop(extcodesize(0)) + extcodecopy(0, 1, 2, 3) + pop(returndatasize()) + returndatacopy(0, 1, 2) + pop(extcodehash(0)) + pop(create(0, 1, 2)) + pop(create2(0, 1, 2, 3)) + pop(call(0, 1, 2, 3, 4, 5, 6)) + pop(callcode(0, 1, 2, 3, 4, 5, 6)) + pop(delegatecall(0, 1, 2, 3, 4, 5)) + pop(staticcall(0, 1, 2, 3, 4, 5)) + return(0, 1) + revert(0, 1) + selfdestruct(0) + invalid() + log0(0, 1) + log1(0, 1, 2) + log2(0, 1, 2, 3) + log3(0, 1, 2, 3, 4) + log4(0, 1, 2, 3, 4, 5) + pop(chainid()) + pop(basefee()) + pop(origin()) + pop(gasprice()) + pop(blockhash(0)) + pop(coinbase()) + pop(timestamp()) + pop(number()) + pop(difficulty()) + pop(gaslimit()) + + // NOTE: msize() is allowed only with optimizer disabled + //pop(msize()) + //pop(pc()) + } + } +} +// ==== +// EVMVersion: >=london +// ---- +// Warning 5740: (89-1716): Unreachable code. +// Warning 5740: (1729-1741): Unreachable code. +// Warning 5740: (1754-1769): Unreachable code. +// Warning 5740: (1782-1791): Unreachable code. +// Warning 5740: (1804-2215): Unreachable code. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure.sol new file mode 100644 index 000000000..85d7b6300 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure.sol @@ -0,0 +1,90 @@ +contract C { + function f() public pure { + assembly { + stop() + pop(add(0, 1)) + pop(sub(0, 1)) + pop(mul(0, 1)) + pop(div(0, 1)) + pop(sdiv(0, 1)) + pop(mod(0, 1)) + pop(smod(0, 1)) + pop(exp(0, 1)) + pop(not(0)) + pop(lt(0, 1)) + pop(gt(0, 1)) + pop(slt(0, 1)) + pop(sgt(0, 1)) + pop(eq(0, 1)) + pop(iszero(0)) + pop(and(0, 1)) + pop(or(0, 1)) + pop(xor(0, 1)) + pop(byte(0, 1)) + pop(shl(0, 1)) + pop(shr(0, 1)) + pop(sar(0, 1)) + pop(addmod(0, 1, 2)) + pop(mulmod(0, 1, 2)) + pop(signextend(0, 1)) + pop(keccak256(0, 1)) + pop(0) + pop(mload(0)) + mstore(0, 1) + mstore8(0, 1) + //pop(sload(0)) + //sstore(0, 1) + //pop(gas()) + //pop(address()) + //pop(balance(0)) + //pop(selfbalance()) + //pop(caller()) + //pop(callvalue()) + pop(calldataload(0)) + pop(calldatasize()) + calldatacopy(0, 1, 2) + pop(codesize()) + codecopy(0, 1, 2) + //pop(extcodesize(0)) + //extcodecopy(0, 1, 2, 3) + pop(returndatasize()) + returndatacopy(0, 1, 2) + //pop(extcodehash(0)) + //pop(create(0, 1, 2)) + //pop(create2(0, 1, 2, 3)) + //pop(call(0, 1, 2, 3, 4, 5, 6)) + //pop(callcode(0, 1, 2, 3, 4, 5, 6)) + //pop(delegatecall(0, 1, 2, 3, 4, 5)) + //pop(staticcall(0, 1, 2, 3, 4, 5)) + return(0, 1) + revert(0, 1) + //selfdestruct(0) + invalid() + //log0(0, 1) + //log1(0, 1, 2) + //log2(0, 1, 2, 3) + //log3(0, 1, 2, 3, 4) + //log4(0, 1, 2, 3, 4, 5) + //pop(chainid()) + //pop(basefee()) + //pop(origin()) + //pop(gasprice()) + //pop(blockhash(0)) + //pop(coinbase()) + //pop(timestamp()) + //pop(number()) + //pop(difficulty()) + //pop(gaslimit()) + + // NOTE: msize() is allowed only with optimizer disabled + //pop(msize()) + //pop(pc()) + } + } +} +// ==== +// EVMVersion: >=london +// ---- +// Warning 5740: (94-1755): Unreachable code. +// Warning 5740: (1768-1780): Unreachable code. +// Warning 5740: (1823-1832): Unreachable code. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_view.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_view.sol new file mode 100644 index 000000000..af30bc21a --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_view.sol @@ -0,0 +1,91 @@ +contract C { + function f() public view { + assembly { + stop() + pop(add(0, 1)) + pop(sub(0, 1)) + pop(mul(0, 1)) + pop(div(0, 1)) + pop(sdiv(0, 1)) + pop(mod(0, 1)) + pop(smod(0, 1)) + pop(exp(0, 1)) + pop(not(0)) + pop(lt(0, 1)) + pop(gt(0, 1)) + pop(slt(0, 1)) + pop(sgt(0, 1)) + pop(eq(0, 1)) + pop(iszero(0)) + pop(and(0, 1)) + pop(or(0, 1)) + pop(xor(0, 1)) + pop(byte(0, 1)) + pop(shl(0, 1)) + pop(shr(0, 1)) + pop(sar(0, 1)) + pop(addmod(0, 1, 2)) + pop(mulmod(0, 1, 2)) + pop(signextend(0, 1)) + pop(keccak256(0, 1)) + pop(0) + pop(mload(0)) + mstore(0, 1) + mstore8(0, 1) + pop(sload(0)) + //sstore(0, 1) + pop(gas()) + pop(address()) + pop(balance(0)) + pop(selfbalance()) + pop(caller()) + pop(callvalue()) + pop(calldataload(0)) + pop(calldatasize()) + calldatacopy(0, 1, 2) + pop(codesize()) + codecopy(0, 1, 2) + pop(extcodesize(0)) + extcodecopy(0, 1, 2, 3) + pop(returndatasize()) + returndatacopy(0, 1, 2) + pop(extcodehash(0)) + //pop(create(0, 1, 2)) + //pop(create2(0, 1, 2, 3)) + //pop(call(0, 1, 2, 3, 4, 5, 6)) + //pop(callcode(0, 1, 2, 3, 4, 5, 6)) + //pop(delegatecall(0, 1, 2, 3, 4, 5)) + pop(staticcall(0, 1, 2, 3, 4, 5)) + return(0, 1) + revert(0, 1) + //selfdestruct(0) + invalid() + //log0(0, 1) + //log1(0, 1, 2) + //log2(0, 1, 2, 3) + //log3(0, 1, 2, 3, 4) + //log4(0, 1, 2, 3, 4, 5) + pop(chainid()) + pop(basefee()) + pop(origin()) + pop(gasprice()) + pop(blockhash(0)) + pop(coinbase()) + pop(timestamp()) + pop(number()) + pop(difficulty()) + pop(gaslimit()) + + // NOTE: msize() is allowed only with optimizer disabled + //pop(msize()) + //pop(pc()) + } + } +} +// ==== +// EVMVersion: >=london +// ---- +// Warning 5740: (94-1733): Unreachable code. +// Warning 5740: (1746-1758): Unreachable code. +// Warning 5740: (1801-1810): Unreachable code. +// Warning 5740: (1978-2244): Unreachable code. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed.sol new file mode 100644 index 000000000..992a51ad2 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed.sol @@ -0,0 +1,28 @@ +contract C { + function f() public { + assembly { + datasize(0) + dataoffset(0) + datacopy(0, 1, 2) + setimmutable(0, "x", 1) + loadimmutable("x") + linkersymbol("x") + memoryguard(0) + verbatim_1i_1o(hex"600202", 0) + + pop(msize()) + pop(pc()) + } + } +} +// ---- +// SyntaxError 6553: (47-362): The msize instruction cannot be used when the Yul optimizer is activated because it can change its semantics. Either disable the Yul optimizer or do not use the instruction. +// DeclarationError 4619: (70-78): Function "datasize" not found. +// DeclarationError 4619: (94-104): Function "dataoffset" not found. +// DeclarationError 4619: (120-128): Function "datacopy" not found. +// DeclarationError 4619: (150-162): Function "setimmutable" not found. +// DeclarationError 4619: (186-199): Function "loadimmutable" not found. +// DeclarationError 4619: (217-229): Function "linkersymbol" not found. +// DeclarationError 4619: (247-258): Function "memoryguard" not found. +// DeclarationError 4619: (274-288): Function "verbatim_1i_1o" not found. +// SyntaxError 2450: (347-349): PC instruction is a low-level EVM feature. Because of that PC is disallowed in strict assembly. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_pure.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_pure.sol new file mode 100644 index 000000000..f9a652fdb --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_pure.sol @@ -0,0 +1,80 @@ +contract C { + function f() public pure { + assembly { + pop(sload(0)) + sstore(0, 1) + pop(gas()) + pop(address()) + pop(balance(0)) + pop(selfbalance()) + pop(caller()) + pop(callvalue()) + pop(extcodesize(0)) + extcodecopy(0, 1, 2, 3) + pop(extcodehash(0)) + pop(create(0, 1, 2)) + pop(create2(0, 1, 2, 3)) + pop(call(0, 1, 2, 3, 4, 5, 6)) + pop(callcode(0, 1, 2, 3, 4, 5, 6)) + pop(delegatecall(0, 1, 2, 3, 4, 5)) + pop(staticcall(0, 1, 2, 3, 4, 5)) + selfdestruct(0) + log0(0, 1) + log1(0, 1, 2) + log2(0, 1, 2, 3) + log3(0, 1, 2, 3, 4) + log4(0, 1, 2, 3, 4, 5) + pop(chainid()) + pop(basefee()) + pop(origin()) + pop(gasprice()) + pop(blockhash(0)) + pop(coinbase()) + pop(timestamp()) + pop(number()) + pop(difficulty()) + pop(gaslimit()) + + // These two are disallowed too but the error suppresses other errors. + //pop(msize()) + //pop(pc()) + } + } +} +// ==== +// EVMVersion: >=london +// ---- +// Warning 5740: (672-1083): Unreachable code. +// TypeError 2527: (79-87): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 8961: (101-113): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 2527: (130-135): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (153-162): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (180-190): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (208-221): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (239-247): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (265-276): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (294-308): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (322-345): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (362-376): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 8961: (394-409): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (427-446): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (464-489): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (507-536): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (554-584): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 2527: (602-630): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 8961: (644-659): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (672-682): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (695-708): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (721-737): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (750-769): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (782-804): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 2527: (821-830): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (848-857): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (875-883): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (901-911): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (929-941): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (959-969): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (987-998): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1016-1024): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1042-1054): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1072-1082): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_view.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_view.sol new file mode 100644 index 000000000..5daf91742 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_view.sol @@ -0,0 +1,38 @@ +contract C { + function f() public view { + assembly { + sstore(0, 1) + pop(create(0, 1, 2)) + pop(create2(0, 1, 2, 3)) + pop(call(0, 1, 2, 3, 4, 5, 6)) + pop(callcode(0, 1, 2, 3, 4, 5, 6)) + pop(delegatecall(0, 1, 2, 3, 4, 5)) + selfdestruct(0) + log0(0, 1) + log1(0, 1, 2) + log2(0, 1, 2, 3) + log3(0, 1, 2, 3, 4) + log4(0, 1, 2, 3, 4, 5) + + // These two are disallowed too but the error suppresses other errors. + //pop(msize()) + //pop(pc()) + } + } +} +// ==== +// EVMVersion: >=london +// ---- +// Warning 5740: (336-468): Unreachable code. +// TypeError 8961: (75-87): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (104-119): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (137-156): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (174-199): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (217-246): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (264-294): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (308-323): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (336-346): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (359-372): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (385-401): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (414-433): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (446-468): Function cannot be declared as view because this expression (potentially) modifies the state. From f567eb1fb2a24dbd956fe65512d4b9015ba7f5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 25 Mar 2022 20:21:06 +0100 Subject: [PATCH 02/21] Disallow RETURNDATASIZE and RETURNDATACOPY in inline assembly blocks in pure functions --- Changelog.md | 1 + libevmasm/SemanticInformation.cpp | 2 + .../inlineAssembly/evm_byzantium.sol | 2 +- .../evm_byzantium_on_homestead.sol | 2 +- ...ze_as_variable_call_post_byzantium.sol.sol | 2 +- ...urndatasize_as_variable_post_byzantium.sol | 2 +- ...turndatasize_as_variable_pre_byzantium.sol | 2 +- ...ine_assembly_instructions_allowed_pure.sol | 10 ++-- ..._assembly_instructions_disallowed_pure.sol | 52 ++++++++++--------- 9 files changed, 41 insertions(+), 34 deletions(-) diff --git a/Changelog.md b/Changelog.md index 203081823..4327a5709 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,6 +12,7 @@ Compiler Features: Bugfixes: * Assembly-Json: Fix assembly json export to store jump types of operations in `jumpType` field instead of `value`. * TypeChecker: Convert parameters of function type to how they would be called for ``abi.encodeCall``. +* View Pure Checker: Mark ``returndatasize`` and ``returndatacopy`` as view to disallow them in inline assembly blocks in pure functions. diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index acbcaa5c0..a2a2b1af2 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -479,6 +479,8 @@ bool SemanticInformation::invalidInPureFunctions(Instruction _instruction) case Instruction::EXTCODESIZE: case Instruction::EXTCODECOPY: case Instruction::EXTCODEHASH: + case Instruction::RETURNDATASIZE: + case Instruction::RETURNDATACOPY: case Instruction::BLOCKHASH: case Instruction::COINBASE: case Instruction::TIMESTAMP: diff --git a/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium.sol b/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium.sol index b08172ebe..28d336577 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium.sol @@ -1,5 +1,5 @@ contract C { - function f() pure external { + function f() view external { assembly { let s := returndatasize() returndatacopy(0, 0, s) diff --git a/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium_on_homestead.sol b/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium_on_homestead.sol index 74316c526..5698f990f 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium_on_homestead.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium_on_homestead.sol @@ -1,5 +1,5 @@ contract C { - function f() pure external { + function f() view external { assembly { let s := returndatasize() returndatacopy(0, 0, s) diff --git a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_call_post_byzantium.sol.sol b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_call_post_byzantium.sol.sol index f959537b6..403022846 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_call_post_byzantium.sol.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_call_post_byzantium.sol.sol @@ -1,5 +1,5 @@ contract C { - function f() public pure { + function f() public view { uint returndatasize; returndatasize; assembly { diff --git a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_post_byzantium.sol b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_post_byzantium.sol index 44a12aa06..903da7223 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_post_byzantium.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_post_byzantium.sol @@ -1,5 +1,5 @@ contract C { - function f() public pure { + function f() public view { uint returndatasize; returndatasize; assembly { diff --git a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_pre_byzantium.sol b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_pre_byzantium.sol index 07bae8ac8..250a0755f 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_pre_byzantium.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_pre_byzantium.sol @@ -1,4 +1,4 @@ -contract C { function f() public pure { uint returndatasize; returndatasize; assembly { pop(returndatasize()) }}} +contract C { function f() public view { uint returndatasize; returndatasize; assembly { pop(returndatasize()) }}} // ==== // EVMVersion: =homestead // ---- diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure.sol index 85d7b6300..9c09bcc37 100644 --- a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure.sol +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure.sol @@ -47,8 +47,8 @@ contract C { codecopy(0, 1, 2) //pop(extcodesize(0)) //extcodecopy(0, 1, 2, 3) - pop(returndatasize()) - returndatacopy(0, 1, 2) + //pop(returndatasize()) + //returndatacopy(0, 1, 2) //pop(extcodehash(0)) //pop(create(0, 1, 2)) //pop(create2(0, 1, 2, 3)) @@ -85,6 +85,6 @@ contract C { // ==== // EVMVersion: >=london // ---- -// Warning 5740: (94-1755): Unreachable code. -// Warning 5740: (1768-1780): Unreachable code. -// Warning 5740: (1823-1832): Unreachable code. +// Warning 5740: (94-1759): Unreachable code. +// Warning 5740: (1772-1784): Unreachable code. +// Warning 5740: (1827-1836): Unreachable code. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_pure.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_pure.sol index f9a652fdb..8da17390d 100644 --- a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_pure.sol +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_pure.sol @@ -11,6 +11,8 @@ contract C { pop(callvalue()) pop(extcodesize(0)) extcodecopy(0, 1, 2, 3) + pop(returndatasize()) + returndatacopy(0, 1, 2) pop(extcodehash(0)) pop(create(0, 1, 2)) pop(create2(0, 1, 2, 3)) @@ -44,7 +46,7 @@ contract C { // ==== // EVMVersion: >=london // ---- -// Warning 5740: (672-1083): Unreachable code. +// Warning 5740: (742-1153): Unreachable code. // TypeError 2527: (79-87): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". // TypeError 8961: (101-113): Function cannot be declared as pure because this expression (potentially) modifies the state. // TypeError 2527: (130-135): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". @@ -55,26 +57,28 @@ contract C { // TypeError 2527: (265-276): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". // TypeError 2527: (294-308): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". // TypeError 2527: (322-345): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 2527: (362-376): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 8961: (394-409): Function cannot be declared as pure because this expression (potentially) modifies the state. -// TypeError 8961: (427-446): Function cannot be declared as pure because this expression (potentially) modifies the state. -// TypeError 8961: (464-489): Function cannot be declared as pure because this expression (potentially) modifies the state. -// TypeError 8961: (507-536): Function cannot be declared as pure because this expression (potentially) modifies the state. -// TypeError 8961: (554-584): Function cannot be declared as pure because this expression (potentially) modifies the state. -// TypeError 2527: (602-630): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 8961: (644-659): Function cannot be declared as pure because this expression (potentially) modifies the state. -// TypeError 8961: (672-682): Function cannot be declared as pure because this expression (potentially) modifies the state. -// TypeError 8961: (695-708): Function cannot be declared as pure because this expression (potentially) modifies the state. -// TypeError 8961: (721-737): Function cannot be declared as pure because this expression (potentially) modifies the state. -// TypeError 8961: (750-769): Function cannot be declared as pure because this expression (potentially) modifies the state. -// TypeError 8961: (782-804): Function cannot be declared as pure because this expression (potentially) modifies the state. -// TypeError 2527: (821-830): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 2527: (848-857): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 2527: (875-883): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 2527: (901-911): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 2527: (929-941): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 2527: (959-969): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 2527: (987-998): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 2527: (1016-1024): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 2527: (1042-1054): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". -// TypeError 2527: (1072-1082): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (362-378): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (392-415): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (432-446): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 8961: (464-479): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (497-516): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (534-559): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (577-606): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (624-654): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 2527: (672-700): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 8961: (714-729): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (742-752): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (765-778): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (791-807): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (820-839): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (852-874): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 2527: (891-900): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (918-927): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (945-953): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (971-981): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (999-1011): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1029-1039): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1057-1068): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1086-1094): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1112-1124): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1142-1152): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". From afd9feead48a28d756e649c21cff1a7ebef897e9 Mon Sep 17 00:00:00 2001 From: Marenz Date: Tue, 15 Mar 2022 18:52:59 +0100 Subject: [PATCH 03/21] LSP.py: Implement simple send/respond framework --- scripts/tests.sh | 2 +- test/libsolidity/lsp/goto_definition.sol | 139 +++ .../lsp/goto_definition_imports.sol | 52 + test/libsolidity/lsp/lib.sol | 8 + .../libsolidity/lsp/publish_diagnostics_1.sol | 2 + .../libsolidity/lsp/publish_diagnostics_2.sol | 2 + test/lsp.py | 1065 ++++++++++++----- 7 files changed, 948 insertions(+), 322 deletions(-) diff --git a/scripts/tests.sh b/scripts/tests.sh index 5515a6869..80559f5e2 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -84,7 +84,7 @@ printTask "Testing Python scripts..." "$REPO_ROOT/test/pyscriptTests.py" printTask "Testing LSP..." -"$REPO_ROOT/scripts/test_solidity_lsp.py" "${SOLIDITY_BUILD_DIR}/solc/solc" +"$REPO_ROOT/test/lsp.py" "${SOLIDITY_BUILD_DIR}/solc/solc" printTask "Running commandline tests..." # Only run in parallel if this is run on CI infrastructure diff --git a/test/libsolidity/lsp/goto_definition.sol b/test/libsolidity/lsp/goto_definition.sol index 675c1297c..7415d3866 100644 --- a/test/libsolidity/lsp/goto_definition.sol +++ b/test/libsolidity/lsp/goto_definition.sol @@ -2,13 +2,16 @@ pragma solidity >=0.8.0; import "./lib.sol"; +// ^ @importDirective interface I { function f(uint x) external returns (uint); + // ^ @functionF } contract IA is I + // ^^ @IASymbol { function f(uint x) public pure override returns (uint) { return x + 1; } } @@ -21,6 +24,7 @@ contract IB is I library IntLib { function add(int self, int b) public pure returns (int) { return self + b; } + // ^^^ @IntLibAdd } contract C @@ -29,40 +33,175 @@ contract C function virtual_inheritance() public payable { obj = new IA(); + // ^ @usingIASymbol obj.f(1); // goto-definition should jump to definition of interface. + // ^ @virtualFunctionLookup } using IntLib for *; function using_for(int i) pure public { i.add(5); + // ^ @usingIntAdd 14.add(4); } function useLib(uint n) public payable returns (uint) { return Lib.add(n, 1); + // ^ @LibSymbol + // ^ @LibAddSymbol } function enums(Color c) public pure returns (Color d) + // ^ @ColorSymbolInParameter { Color e = Color.Red; + // ^ @eVariableDeclaration + // ^ @RedEnumMemberAccess if (c == e) + // ^ @eVariableAccess d = Color.Green; else d = c; } type Price is uint128; + // ^^^^^ @PriceDeclaration function udlTest() public pure returns (uint128) { Price p = Price.wrap(128); + // ^ @PriceSymbol + // ^ @PriceInWrap return Price.unwrap(p); } function structCtorTest(uint8 v) public pure returns (uint8 result) { RGBColor memory c = RGBColor(v, 2 * v, 3 * v); + // ^ @RGBColorCursor result = c.red; + int a; +// ^^^^^ @unusedLocalVar } } +// ---- +// goto_definition: @unusedLocalVar 2072 +// lib: @diagnostics 2072 +// -> textDocument/definition { +// "position": @importDirective +// } +// <- [ +// { +// "range": { +// "end": { +// "character": 0, +// "line": 0 +// }, +// "start": { +// "character": 0, +// "line": 0 +// } +// }, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @usingIASymbol +// } +// <- [ +// { +// "range": @IASymbol, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @virtualFunctionLookup +// } +// <- [ +// { +// "range": @functionF, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @usingIntAdd +// } +// <- [ +// { +// "range": @IntLibAdd, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @LibSymbol +// } +// <- [ +// { +// "range": @LibLibrary, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @LibAddSymbol +// } +// <- [ +// { +// "range": @addSymbol, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @ColorSymbolInParameter +// } +// <- [ +// { +// "range": @ColorEnum, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @RedEnumMemberAccess +// } +// <- [ +// { +// "range": @EnumMemberRed, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @eVariableAccess +// } +// <- [ +// { +// "range": @eVariableDeclaration, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @PriceSymbol +// } +// <- [ +// { +// "range": @PriceDeclaration, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @PriceInWrap +// } +// <- [ +// { +// "range": @PriceDeclaration, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @RGBColorCursor +// } +// <- [ +// { +// "range": @RGBColorStruct, +// "uri": "lib.sol" +// } +// ] diff --git a/test/libsolidity/lsp/goto_definition_imports.sol b/test/libsolidity/lsp/goto_definition_imports.sol index b3df921fe..dd0267d71 100644 --- a/test/libsolidity/lsp/goto_definition_imports.sol +++ b/test/libsolidity/lsp/goto_definition_imports.sol @@ -2,18 +2,70 @@ pragma solidity >=0.8.0; import {Weather as Wetter} from "./lib.sol"; +// ^ @wheatherImportCursor import "./lib.sol" as That; +// ^^^^ @ThatImport contract C { function test_symbol_alias() public pure returns (Wetter result) + // ^ @WetterCursor { result = Wetter.Sunny; } function test_library_alias() public pure returns (That.Color result) + // ^ @ThatCursor { That.Color color = That.Color.Red; +// ^ @ThatVarCursor ^ @ThatExpressionCursor result = color; } } +// ---- +// lib: @diagnostics 2072 +// -> textDocument/definition { +// "position": @wheatherImportCursor +// } +// <- [ +// { +// "range": @whetherEnum, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @WetterCursor +// } +// <- [ +// { +// "range": @whetherEnum, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @ThatCursor +// } +// <- [ +// { +// "range": @ColorEnum, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @ThatVarCursor +// } +// <- [ +// { +// "range": @ColorEnum, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @ThatExpressionCursor +// } +// <- [ +// { +// "range": @ThatImport, +// "uri": "goto_definition_imports.sol" +// } +// ] diff --git a/test/libsolidity/lsp/lib.sol b/test/libsolidity/lsp/lib.sol index 031cf19ad..c476f2dc2 100644 --- a/test/libsolidity/lsp/lib.sol +++ b/test/libsolidity/lsp/lib.sol @@ -5,6 +5,7 @@ pragma solidity >=0.8.0; error E(uint, uint); enum Weather { +// ^^^^^^^ @whetherEnum Sunny, Cloudy, Rainy @@ -12,8 +13,10 @@ enum Weather { /// Some custom Color enum type holding 3 colors. enum Color { +// ^^^^^ @ColorEnum /// Red color. Red, +// ^^^ @EnumMemberRed /// Green color. Green, /// Blue color. @@ -21,9 +24,11 @@ enum Color { } library Lib +// @ ^^^ @LibLibrary { function add(uint a, uint b) public pure returns (uint result) // ^( @addFunction +// ^^^ @addSymbol { result = a + b; } @@ -37,8 +42,11 @@ library Lib } struct RGBColor +// ^^^^^^^^ @RGBColorStruct { uint8 red; uint8 green; uint8 blue; } +// ---- +// lib: @diagnostics 2072 diff --git a/test/libsolidity/lsp/publish_diagnostics_1.sol b/test/libsolidity/lsp/publish_diagnostics_1.sol index be15a4090..357de7a35 100644 --- a/test/libsolidity/lsp/publish_diagnostics_1.sol +++ b/test/libsolidity/lsp/publish_diagnostics_1.sol @@ -19,3 +19,5 @@ contract D // ^^^^^^^^^^^^ @unusedContractVariable } } +// ---- +// publish_diagnostics_1: @unusedReturnVariable 6321 @unusedVariable 2072 @unusedContractVariable 2072 diff --git a/test/libsolidity/lsp/publish_diagnostics_2.sol b/test/libsolidity/lsp/publish_diagnostics_2.sol index 65b4df585..cf052e7c9 100644 --- a/test/libsolidity/lsp/publish_diagnostics_2.sol +++ b/test/libsolidity/lsp/publish_diagnostics_2.sol @@ -22,3 +22,5 @@ contract D // ^^^^^^^^^^^^^^^^^^^^^ @wrongArgumentsCount } } +// ---- +// publish_diagnostics_2: @conversionError 9574 @argumentsRequired 6777 @wrongArgumentsCount 6160 diff --git a/test/lsp.py b/test/lsp.py index 8b7398cf2..79be59f8e 100755 --- a/test/lsp.py +++ b/test/lsp.py @@ -8,13 +8,93 @@ import subprocess import sys import traceback import re - +import tty +import functools +from collections import namedtuple +from copy import deepcopy from typing import Any, List, Optional, Tuple, Union +from itertools import islice + from enum import Enum, auto import colorama # Enables the use of SGR & CUP terminal VT sequences on Windows. from deepdiff import DeepDiff +""" +Named tuple that holds various regexes used to parse the test specification. +""" +TestRegexesTuple = namedtuple("TestRegexesTuple", [ + "sendRequest", # regex to find requests to be sent & tested + "findQuotedTag", # regex to find tags wrapped in quotes + "findTag", # regex to find tags + "fileDiagnostics", # regex to find diagnostic expectations for a file + "diagnostic" # regex to find a single diagnostic within the file expectations +]) +""" +Instance of the named tuple holding the regexes +""" +TEST_REGEXES = TestRegexesTuple( + re.compile(R'^// -> (?P[\w\/]+) {'), + re.compile(R'(?P"@\w+")'), + re.compile(R'(?P@\w+)'), + re.compile(R'// (?P\w+):[ ]?(?P[\w @]*)'), + re.compile(R'(?P@\w+) (?P\d\d\d\d)') +) + +""" +Named tuple holding regexes to find tags in the solidity code +""" +TagRegexesTuple = namedtuple("TagRegexestuple", ["simpleRange", "multilineRange"]) +TAG_REGEXES = TagRegexesTuple( + re.compile(R"(?P[\^]+) (?P@\w+)"), + re.compile(R"\^(?P[()]{1,2}) (?P@\w+)$") +) + + +def count_index(lines, start=0): + """ + Takes an iterable of lines and adds the current byte index so it's available + when iterating or looping. + """ + n = start + for elem in lines: + yield n, elem + n += 1 + len(elem) + +def tags_only(lines, start=0): + """ + Filter the lines for tag comments and report line number that tags refer to. + """ + n = start + numCommentLines = 0 + + def hasTag(line): + if line.find("// ") != -1: + for _, regex in TAG_REGEXES._asdict().items(): + if regex.search(line[len("// "):]) is not None: + return True + return False + + for line in lines: + if hasTag(line): + numCommentLines += 1 + yield n - numCommentLines, line + else: + numCommentLines = 0 + + n += 1 + + +def prepend_comments(sequence): + """ + Prepends a comment indicator to each element + """ + result = "" + for line in sequence.splitlines(True): + result = result + "// " + line + return result + + # {{{ JsonRpcProcess class BadHeader(Exception): def __init__(self, msg: str): @@ -59,7 +139,7 @@ class JsonRpcProcess: while True: # read header line = self.process.stdout.readline() - if line == '': + if len(line) == 0: # server quit return None line = line.decode("utf-8") @@ -118,12 +198,30 @@ SGR_STATUS_OKAY = '\033[1;32m' SGR_STATUS_FAIL = '\033[1;31m' class ExpectationFailed(Exception): - def __init__(self, actual, expected): - self.actual = json.dumps(actual, sort_keys=True) - self.expected = json.dumps(expected, sort_keys=True) - diff = json.dumps(DeepDiff(actual, expected), indent=4) + class Part(Enum): + Diagnostics = auto() + Methods = auto() + + def __init__(self, reason: str, part): + self.part = part + super().__init__(reason) + +class JSONExpectationFailed(ExpectationFailed): + def __init__(self, actual, expected, part): + self.actual = actual + self.expected = expected + + expected_pretty = "" + + if expected is not None: + expected_pretty = json.dumps(expected, sort_keys=True) + + diff = DeepDiff(actual, expected) + super().__init__( - f"\n\tExpected {self.expected}\n\tbut got {self.actual}.\n\t{diff}" + f"\n\tExpected {expected_pretty}" + \ + f"\n\tbut got {json.dumps(actual, sort_keys=True)}.\n\t{diff}", + part ) @@ -180,15 +278,407 @@ class Counter: failed: int = 0 -class Marker(Enum): - SimpleRange = auto() - MultilineRange = auto() - - # Returns the given marker with the end extended by 'amount' def extendEnd(marker, amount=1): - marker["end"]["character"] += amount - return marker + newMarker = deepcopy(marker) + newMarker["end"]["character"] += amount + return newMarker + +class TestParserException(Exception): + def __init__(self, incompleteResult, msg: str): + self.result = incompleteResult + super().__init__("Failed to parse test specification: " + msg) + +class TestParser: + """ + Parses test specifications. + Usage example: + + parsed_testcases = TestParser(content).parse() + + # First diagnostics are yielded + expected_diagnostics = next(parsed_testcases) + ... + # Now each request/response pair in the test definition + for testcase in self.parsed_testcases: + ... + """ + RequestAndResponse = namedtuple('RequestAndResponse', + "method, request, response, responseBegin, responseEnd", + defaults=(None, None, None, None) + ) + Diagnostics = namedtuple('Diagnostics', 'tests start end has_header') + Diagnostic = namedtuple('Diagnostic', 'marker code') + + TEST_START = "// ----" + + def __init__(self, content: str): + self.content = content + self.lines = None + self.current_line_tuple = None + + def parse(self): + """ + Starts parsing the test specifications. + Will first yield with the diagnostics expectations as type 'Diagnostics'. + After that, it will yield once for every Request/Response pair found in + the file, each time as type 'RequestAndResponse'. + + """ + testDefStartIdx = self.content.rfind(f"\n{self.TEST_START}\n") + + if testDefStartIdx == -1: + # Set start/end to end of file if there is no test section + yield self.Diagnostics({}, len(self.content), len(self.content), False) + return + + self.lines = islice( + count_index(self.content[testDefStartIdx+1:].splitlines(), testDefStartIdx+1), + 1, + None + ) + self.next_line() + + yield self.parseDiagnostics() + + while not self.at_end(): + yield self.RequestAndResponse(**self.parseRequestAndResponse()) + self.next_line() + + + def parseDiagnostics(self): + """ + Parse diagnostic expectations specified in the file. + Returns a named tuple instance of "Diagnostics" + """ + diagnostics = { "tests": {}, "has_header": True } + + diagnostics["start"] = self.position() + + while not self.at_end(): + fileDiagMatch = TEST_REGEXES.fileDiagnostics.match(self.current_line()) + if fileDiagMatch is None: + break + + testDiagnostics = [] + + for diagnosticMatch in TEST_REGEXES.diagnostic.finditer(fileDiagMatch.group("diagnostics")): + testDiagnostics.append(self.Diagnostic( + diagnosticMatch.group("tag"), + int(diagnosticMatch.group("code")) + )) + + diagnostics["tests"][fileDiagMatch.group("testname")] = testDiagnostics + + self.next_line() + + diagnostics["end"] = self.position() + return self.Diagnostics(**diagnostics) + + + def parseRequestAndResponse(self): + RESPONSE_START = "// <- " + REQUEST_END = "// }" + COMMENT_PREFIX = "// " + + ret = {} + start_character = None + + # Parse request header + requestResult = TEST_REGEXES.sendRequest.match(self.current_line()) + if requestResult is not None: + ret["method"] = requestResult.group("method") + ret["request"] = "{\n" + else: + raise TestParserException(ret, "Method for request not found") + + self.next_line() + + # Search for request block end + while not self.at_end(): + line = self.current_line() + ret["request"] += line[len(COMMENT_PREFIX):] + "\n" + + self.next_line() + + if line.startswith(REQUEST_END): + break + + # Reached end without finding request_end. Abort. + if self.at_end(): + raise TestParserException(ret, "Request body not found") + + + # Parse response header + if self.current_line().startswith(RESPONSE_START): + start_character = self.current_line()[len(RESPONSE_START)] + if start_character not in ("{", "["): + raise TestParserException(ret, "Response header malformed") + ret["response"] = self.current_line()[len(RESPONSE_START):] + "\n" + ret["responseBegin"] = self.position() + else: + raise TestParserException(ret, "Response header not found") + + self.next_line() + + end_character = "}" if start_character == "{" else "]" + + # Search for request block end + while not self.at_end(): + ret["response"] += self.current_line()[len(COMMENT_PREFIX):] + "\n" + + if self.current_line().startswith(f"// {end_character}"): + ret["responseEnd"] = self.position() + len(self.current_line()) + break + + self.next_line() + + # Reached end without finding block_end. Abort. + if self.at_end(): + raise TestParserException(ret, "Response footer not found") + + return ret + + def next_line(self): + self.current_line_tuple = next(self.lines, None) + + def current_line(self): + return self.current_line_tuple[1] + + def position(self): + """ + Returns current byte position + """ + if self.current_line_tuple is None: + return len(self.content) + return self.current_line_tuple[0] + + def at_end(self): + """ + Returns True if we exhausted the lines + """ + return self.current_line_tuple is None + +class FileTestRunner: + """ + Runs all tests in a given file. + It is required to call test_diagnostics() before calling test_methods(). + + When a test fails, asks the user how to proceed. + Offers automatic test expectation updates and rerunning of the tests. + """ + + class TestResult(Enum): + SuccessOrIgnored = auto() + Reparse = auto() + + def __init__(self, test_name, solc, suite): + self.test_name = test_name + self.suite = suite + self.solc = solc + self.open_tests = [] + self.content = self.suite.get_test_file_contents(self.test_name) + self.markers = self.suite.get_file_tags(self.test_name) + self.parsed_testcases = None + self.expected_diagnostics = None + + def test_diagnostics(self): + """ + Test that the expected diagnostics match the actual diagnostics + """ + try: + self.parsed_testcases = TestParser(self.content).parse() + + # Process diagnostics first + self.expected_diagnostics = next(self.parsed_testcases) + assert isinstance(self.expected_diagnostics, TestParser.Diagnostics) is True + + tests = self.expected_diagnostics.tests + + # Add our own test diagnostics if they didn't exist + if self.test_name not in tests: + tests[self.test_name] = [] + + published_diagnostics = \ + self.suite.open_file_and_wait_for_diagnostics(self.solc, self.test_name) + + for diagnostics in published_diagnostics: + self.open_tests.append(diagnostics["uri"].replace(self.suite.project_root_uri + "/", "")[:-len(".sol")]) + + self.suite.expect_equal( + len(published_diagnostics), + len(tests), + description="Amount of reports does not match!") + + for diagnostics in published_diagnostics: + testname = diagnostics["uri"].replace(self.suite.project_root_uri + "/", "")[:-len(".sol")] + + expected_diagnostics = tests[testname] + self.suite.expect_equal( + len(diagnostics["diagnostics"]), + len(expected_diagnostics), + description="Unexpected amount of diagnostics" + ) + markers = self.suite.get_file_tags(testname) + for actual_diagnostic in diagnostics["diagnostics"]: + expected_diagnostic = next((diagnostic for diagnostic in + expected_diagnostics if actual_diagnostic['range'] == + markers[diagnostic.marker]), None) + + if expected_diagnostic is None: + raise ExpectationFailed( + f"Unexpected diagnostic: {json.dumps(actual_diagnostic, indent=4, sort_keys=True)}", + ExpectationFailed.Part.Diagnostics + ) + + self.suite.expect_diagnostic( + actual_diagnostic, + code=expected_diagnostic.code, + marker=markers[expected_diagnostic.marker] + ) + + except Exception as e: + print(e) + self.close_all_open_files() + raise + + def close_all_open_files(self): + for test in self.open_tests: + self.solc.send_message( + 'textDocument/didClose', + { 'textDocument': { 'uri': self.suite.get_test_file_uri(test) }} + ) + self.suite.wait_for_diagnostics(self.solc) + + self.open_tests.clear() + + def test_methods(self) -> bool: + """ + Test all methods. Returns False if a reparsing is required, else True + """ + try: + # Now handle each request/response pair in the test definition + for testcase in self.parsed_testcases: + try: + self.run_testcase(testcase) + except JSONExpectationFailed as e: + result = self.user_interaction_failed_method_test(testcase, e.actual, e.expected) + + if result == self.TestResult.Reparse: + return False + + return True + except TestParserException as e: + print(e) + print(e.result) + raise + finally: + self.close_all_open_files() + + def user_interaction_failed_method_test(self, testcase, actual, expected): + actual_pretty = self.suite.replace_ranges_with_tags(actual) + + if expected is None: + print("Failed to parse expected response, received:\n" + actual) + else: + print("Expected:\n" + \ + self.suite.replace_ranges_with_tags(expected) + \ + "\nbut got:\n" + actual_pretty + ) + + while True: + print("(u)pdate/(r)etry/(i)gnore?") + user_response = sys.stdin.read(1) + if user_response == "i": + return self.TestResult.SuccessOrIgnored + + if user_response == "u": + actual = actual["result"] + self.content = self.content[:testcase.responseBegin] + \ + prepend_comments("<- " + self.suite.replace_ranges_with_tags(actual)) + \ + self.content[testcase.responseEnd:] + + with open(self.suite.get_test_file_path(self.test_name), mode="w", encoding="utf-8", newline='') as f: + f.write(self.content) + return self.TestResult.Reparse + if user_response == "r": + return self.TestResult.Reparse + + print("Invalid response.") + + + def run_testcase(self, testcase: TestParser.RequestAndResponse): + """ + Runs the given testcase. + """ + requestBodyJson = self.parse_json_with_tags(testcase.request, self.markers) + # add textDocument/uri if missing + if 'textDocument' not in requestBodyJson: + requestBodyJson['textDocument'] = { 'uri': self.suite.get_test_file_uri(self.test_name) } + actualResponseJson = self.solc.call_method(testcase.method, requestBodyJson) + + # simplify response + for result in actualResponseJson["result"]: + result["uri"] = result["uri"].replace(self.suite.project_root_uri + "/", "") + if "jsonrpc" in actualResponseJson: + actualResponseJson.pop("jsonrpc") + + try: + expectedResponseJson = self.parse_json_with_tags(testcase.response, self.markers) + except json.decoder.JSONDecodeError: + expectedResponseJson = None + + expectedResponseJson = { "result": expectedResponseJson } + + self.suite.expect_equal( + actualResponseJson, + expectedResponseJson, + f"Request failed: \n{testcase.request}", + ExpectationFailed.Part.Methods + ) + + + def parse_json_with_tags(self, content, markersFallback): + """ + Replaces any tags with their actual content and parsers the result as + json to return it. + """ + split_by_tag = TEST_REGEXES.findTag.split(content) + + # add quotes so we can parse it as json + contentReplaced = '"'.join(split_by_tag) + contentJson = json.loads(contentReplaced) + + def replace_tag(data, markers): + + if isinstance(data, list): + for el in data: + replace_tag(el, markers) + return data + + # Check if we need markers from a specific file + # Needs to be done before the loop or it might be called only after + # we found "range" or "position" + if "uri" in data: + markers = self.suite.get_file_tags(data["uri"][:-len(".sol")]) + + for key, val in data.items(): + if key == "range": + for tag, tagRange in markers.items(): + if tag == val: + data[key] = tagRange + elif key == "position": + for tag, tagRange in markers.items(): + if tag == val: + data[key] = tagRange["start"] + elif isinstance(val, dict): + replace_tag(val, markers) + elif isinstance(val, list): + for el in val: + replace_tag(el, markers) + return data + + return replace_tag(contentJson, markersFallback) class SolidityLSPTestSuite: # {{{ @@ -198,7 +688,6 @@ class SolidityLSPTestSuite: # {{{ trace_io: bool = False fail_fast: bool = False test_pattern: str - marker_regexes: {} def __init__(self): colorama.init() @@ -210,10 +699,6 @@ class SolidityLSPTestSuite: # {{{ self.trace_io = args.trace_io self.test_pattern = args.test_pattern self.fail_fast = args.fail_fast - self.marker_regexes = { - Marker.SimpleRange: re.compile(R"(?P[\^]+) (?P@\w+)"), - Marker.MultilineRange: re.compile(R"\^(?P[()]) (?P@\w+)$") - } print(f"{SGR_NOTICE}test pattern: {self.test_pattern}{SGR_RESET}") @@ -283,6 +768,9 @@ class SolidityLSPTestSuite: # {{{ params['rootUri'] = None lsp.call_method('initialize', params) lsp.send_notification('initialized') + # Enable traces to receive the amount of expected diagnostics before + # actually receiving them. + lsp.send_message("$/setTrace", { 'value': 'messages' }) # {{{ helpers def get_test_file_path(self, test_case_name): @@ -314,35 +802,86 @@ class SolidityLSPTestSuite: # {{{ raise RuntimeError(f"Error {code} received. {text}") if 'method' not in message.keys(): raise RuntimeError("No method received but something else.") - self.expect_equal(message['method'], method_name, "Ensure expected method name") + self.expect_equal(message['method'], method_name, description="Ensure expected method name") return message['params'] - def wait_for_diagnostics(self, solc: JsonRpcProcess, count: int) -> List[dict]: + def wait_for_diagnostics(self, solc: JsonRpcProcess) -> List[dict]: """ - Return `count` number of published diagnostic reports sorted by file URI. + Return all published diagnostic reports sorted by file URI. """ reports = [] - for _ in range(0, count): + + num_files = solc.receive_message()["params"]["openFileCount"] + + for _ in range(0, num_files): message = solc.receive_message() + assert message is not None # This can happen if the server aborts early. + reports.append( self.require_params_for_method( 'textDocument/publishDiagnostics', message, ) ) + return sorted(reports, key=lambda x: x['uri']) + def fetch_and_format_diagnostics(self, solc: JsonRpcProcess, test): + expectations = "" + + published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, test) + + for diagnostics in published_diagnostics: + testname = diagnostics["uri"].replace(self.project_root_uri + "/", "")[:-len(".sol")] + + # Skip empty diagnostics within the same file + if len(diagnostics["diagnostics"]) == 0 and testname == test: + continue + + expectations += f"// {testname}:" + + for diagnostic in diagnostics["diagnostics"]: + tag = self.find_tag_with_range(testname, diagnostic['range']) + + if tag is None: + raise Exception(f"No tag found for diagnostic range {diagnostic['range']}") + + expectations += f" {tag} {diagnostic['code']}" + expectations += "\n" + + return expectations + + def update_diagnostics_in_file( + self, + solc: JsonRpcProcess, + test, + content, + current_diagnostics: TestParser.Diagnostics + ): + test_header = "" + + if not current_diagnostics.has_header: + test_header = f"{TestParser.TEST_START}\n" + + content = content[:current_diagnostics.start] + \ + test_header + \ + self.fetch_and_format_diagnostics(solc, test) + \ + content[current_diagnostics.end:] + + with open(self.get_test_file_path(test), mode="w", encoding="utf-8", newline='') as f: + f.write(content) + + return content + def open_file_and_wait_for_diagnostics( self, solc_process: JsonRpcProcess, test_case_name: str, - max_diagnostic_reports: int = 1 ) -> List[Any]: """ Opens file for given test case and waits for diagnostics to be published. """ - assert max_diagnostic_reports > 0 solc_process.send_message( 'textDocument/didOpen', { @@ -355,9 +894,15 @@ class SolidityLSPTestSuite: # {{{ } } ) - return self.wait_for_diagnostics(solc_process, max_diagnostic_reports) + return self.wait_for_diagnostics(solc_process) - def expect_equal(self, actual, expected, description="Equality") -> None: + def expect_equal( + self, + actual, + expected, + description="Equality", + part=ExpectationFailed.Part.Diagnostics + ) -> None: self.assertion_counter.total += 1 prefix = f"[{self.assertion_counter.total}] {SGR_ASSERT_BEGIN}{description}: " diff = DeepDiff(actual, expected) @@ -370,7 +915,7 @@ class SolidityLSPTestSuite: # {{{ # Failed assertions are always printed. self.assertion_counter.failed += 1 print(prefix + SGR_STATUS_FAIL + 'FAILED' + SGR_RESET) - raise ExpectationFailed(actual, expected) + raise JSONExpectationFailed(actual, expected, part) def expect_empty_diagnostics(self, published_diagnostics: List[dict]) -> None: self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") @@ -384,10 +929,21 @@ class SolidityLSPTestSuite: # {{{ startEndColumns: Tuple[int, int] = None, marker: {} = None ): - self.expect_equal(diagnostic['code'], code, f'diagnostic: {code}') + self.expect_equal( + diagnostic['code'], + code, + ExpectationFailed.Part.Diagnostics, + f'diagnostic: {code}' + ) if marker: - self.expect_equal(diagnostic['range'], marker, "diagnostic: check range") + self.expect_equal( + diagnostic['range'], + marker, + ExpectationFailed.Part.Diagnostics, + "diagnostic: check range" + ) + else: assert len(startEndColumns) == 2 [startColumn, endColumn] = startEndColumns @@ -397,6 +953,7 @@ class SolidityLSPTestSuite: # {{{ 'start': {'character': startColumn, 'line': lineNo}, 'end': {'character': endColumn, 'line': lineNo} }, + ExpectationFailed.Part.Diagnostics, "diagnostic: check range" ) @@ -446,44 +1003,102 @@ class SolidityLSPTestSuite: # {{{ message = "Goto definition (" + description + ")" self.expect_equal(len(response['result']), 1, message) self.expect_location(response['result'][0], expected_uri, expected_lineNo, expected_startEndColumns) + + + def find_tag_with_range(self, test, target_range): + """ + Find and return the tag that represents the requested range otherwise + return None. + """ + markers = self.get_file_tags(test) + + for tag, tag_range in markers.items(): + if tag_range == target_range: + return str(tag) + + return None + + def replace_ranges_with_tags(self, content): + """ + Replace matching ranges with "@". + """ + + def recursive_iter(obj): + if isinstance(obj, dict): + yield obj + for item in obj.values(): + yield from recursive_iter(item) + elif any(isinstance(obj, t) for t in (list, tuple)): + for item in obj: + yield from recursive_iter(item) + + for item in recursive_iter(content): + if "uri" in item and "range" in item: + markers = self.get_file_tags(item["uri"][:-len(".sol")]) + for tag, tagRange in markers.items(): + if tagRange == item["range"]: + item["range"] = str(tag) + + # Convert JSON to string and split it at the quoted tags + split_by_tag = TEST_REGEXES.findQuotedTag.split(json.dumps(content, indent=4, sort_keys=True)) + + # remove the quotes and return result + return "".join(map(lambda p: p[1:-1] if p.startswith('"@') else p, split_by_tag)) + + def user_interaction_failed_diagnostics( + self, + solc: JsonRpcProcess, + test, + content, + current_diagnostics: TestParser.Diagnostics + ): + """ + Asks the user how to proceed after an error. + Returns True if the test/file should be ignored, otherwise False + """ + while True: + print("(u)pdate/(r)etry/(s)kip file?") + user_response = sys.stdin.read(1) + if user_response == "u": + while True: + try: + self.update_diagnostics_in_file(solc, test, content, current_diagnostics) + return False + # pragma pylint: disable=broad-except + except Exception as e: + print(e) + if ret := self.user_interaction_failed_autoupdate(test): + return ret + elif user_response == 's': + return True + elif user_response == 'r': + return False + + def user_interaction_failed_autoupdate(self, test): + print("(e)dit/(r)etry/(s)kip file?") + user_response = sys.stdin.read(1) + if user_response == "r": + print("retrying...") + # pragma pylint: disable=no-member + self.get_file_tags.cache_clear() + return False + if user_response == "e": + editor = os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')) + subprocess.run( + f'{editor} {self.get_test_file_path(test)}', + shell=True, + check=True + ) + # pragma pylint: disable=no-member + self.get_file_tags.cache_clear() + elif user_response == "s": + print("skipping...") + + return True + # }}} # {{{ actual tests - def test_publish_diagnostics_warnings(self, solc: JsonRpcProcess) -> None: - self.setup_lsp(solc) - TEST_NAME = 'publish_diagnostics_1' - published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME) - - self.expect_equal(len(published_diagnostics), 1, "One published_diagnostics message") - report = published_diagnostics[0] - - self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") - diagnostics = report['diagnostics'] - - markers = self.get_file_tags(TEST_NAME) - - self.expect_equal(len(diagnostics), 3, "3 diagnostic messages") - self.expect_diagnostic(diagnostics[0], code=6321, marker=markers["@unusedReturnVariable"]) - self.expect_diagnostic(diagnostics[1], code=2072, marker=markers["@unusedVariable"]) - self.expect_diagnostic(diagnostics[2], code=2072, marker=markers["@unusedContractVariable"]) - - def test_publish_diagnostics_errors(self, solc: JsonRpcProcess) -> None: - self.setup_lsp(solc) - TEST_NAME = 'publish_diagnostics_2' - published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME) - - self.expect_equal(len(published_diagnostics), 1, "One published_diagnostics message") - report = published_diagnostics[0] - - self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") - diagnostics = report['diagnostics'] - - markers = self.get_file_tags(TEST_NAME) - - self.expect_equal(len(diagnostics), 3, "3 diagnostic messages") - self.expect_diagnostic(diagnostics[0], code=9574, marker=markers["@conversionError"]) - self.expect_diagnostic(diagnostics[1], code=6777, marker=markers["@argumentsRequired"]) - self.expect_diagnostic(diagnostics[2], code=6160, marker=markers["@wrongArgumentsCount"]) def test_publish_diagnostics_errors_multiline(self, solc: JsonRpcProcess) -> None: self.setup_lsp(solc) @@ -510,7 +1125,7 @@ class SolidityLSPTestSuite: # {{{ def test_textDocument_didOpen_with_relative_import(self, solc: JsonRpcProcess) -> None: self.setup_lsp(solc) TEST_NAME = 'didOpen_with_import' - published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME, 2) + published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME) self.expect_equal(len(published_diagnostics), 2, "Diagnostic reports for 2 files") @@ -526,7 +1141,7 @@ class SolidityLSPTestSuite: # {{{ marker = self.get_file_tags("lib")["@diagnostics"] self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker) - + @functools.lru_cache # pragma pylint: disable=lru-cache-decorating-method def get_file_tags(self, test_name: str, verbose=False): """ Finds all tags (e.g. @tagname) in the given test and returns them as a @@ -541,14 +1156,12 @@ class SolidityLSPTestSuite: # {{{ markers = {} - for lineNum, line in enumerate(content.splitlines(), start=-1): + for lineNum, line in tags_only(content.splitlines()): commentStart = line.find("//") - if commentStart == -1: - continue - for kind, regex in self.marker_regexes.items(): + for kind, regex in TAG_REGEXES._asdict().items(): for match in regex.finditer(line[commentStart:]): - if kind == Marker.SimpleRange: + if kind == "simpleRange": markers[match.group("tag")] = { "start": { "line": lineNum, @@ -558,7 +1171,7 @@ class SolidityLSPTestSuite: # {{{ "line": lineNum, "character": match.end("range") + commentStart }} - elif kind == Marker.MultilineRange: + elif kind == "multilineRange": if match.group("delimiter") == "(": markers[match.group("tag")] = \ { "start": { "line": lineNum, "character": 0 } } @@ -575,7 +1188,7 @@ class SolidityLSPTestSuite: # {{{ # Reusing another test but now change some file that generates an error in the other. self.test_textDocument_didOpen_with_relative_import(solc) marker = self.get_file_tags("lib")["@addFunction"] - self.open_file_and_wait_for_diagnostics(solc, 'lib', 2) + self.open_file_and_wait_for_diagnostics(solc, 'lib') solc.send_message( 'textDocument/didChange', { @@ -592,7 +1205,7 @@ class SolidityLSPTestSuite: # {{{ ] } ) - published_diagnostics = self.wait_for_diagnostics(solc, 2) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 2, "Diagnostic reports for 2 files") # Main file now contains a new diagnostic @@ -612,7 +1225,7 @@ class SolidityLSPTestSuite: # {{{ def test_textDocument_didOpen_with_relative_import_without_project_url(self, solc: JsonRpcProcess) -> None: self.setup_lsp(solc, expose_project_root=False) TEST_NAME = 'didOpen_with_import' - published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME, 2) + published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME) self.verify_didOpen_with_import_diagnostics(published_diagnostics) def verify_didOpen_with_import_diagnostics( @@ -632,9 +1245,42 @@ class SolidityLSPTestSuite: # {{{ self.expect_equal(report['uri'], self.get_test_file_uri('lib'), "Correct file URI") self.expect_equal(len(report['diagnostics']), 1, "one diagnostic") - marker = self.get_file_tags('lib')["@diagnostics"] + markers = self.get_file_tags('lib') + marker = markers["@diagnostics"] self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker) + def test_generic(self, solc: JsonRpcProcess) -> None: + self.setup_lsp(solc) + + STATIC_TESTS = ['didChange_template', 'didOpen_with_import', 'publish_diagnostics_3'] + + tests = filter( + lambda x: x not in STATIC_TESTS, + map(lambda x: x[:-len(".sol")], os.listdir(self.project_root_dir)) + ) + + for test in tests: + try_again = True + print(f"Running test {test}") + + while try_again: + runner = FileTestRunner(test, solc, self) + + try: + runner.test_diagnostics() + try_again = not runner.test_methods() + except ExpectationFailed as e: + print(e) + + if e.part == e.Part.Diagnostics: + try_again = not self.user_interaction_failed_diagnostics( + solc, + test, + runner.content, + runner.expected_diagnostics + ) + else: + raise def test_textDocument_didChange_updates_diagnostics(self, solc: JsonRpcProcess) -> None: self.setup_lsp(solc) @@ -664,7 +1310,7 @@ class SolidityLSPTestSuite: # {{{ ] } ) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1) report = published_diagnostics[0] self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") @@ -676,7 +1322,7 @@ class SolidityLSPTestSuite: # {{{ def test_textDocument_didChange_delete_line_and_close(self, solc: JsonRpcProcess) -> None: # Reuse this test to prepare and ensure it is as expected self.test_textDocument_didOpen_with_relative_import(solc) - self.open_file_and_wait_for_diagnostics(solc, 'lib', 2) + self.open_file_and_wait_for_diagnostics(solc, 'lib') marker = self.get_file_tags('lib')["@diagnostics"] @@ -697,7 +1343,7 @@ class SolidityLSPTestSuite: # {{{ ] } ) - published_diagnostics = self.wait_for_diagnostics(solc, 2) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 2, "published diagnostics count") report1 = published_diagnostics[0] self.expect_equal(report1['uri'], self.get_test_file_uri('didOpen_with_import'), "Correct file URI") @@ -712,7 +1358,7 @@ class SolidityLSPTestSuite: # {{{ { 'textDocument': { 'uri': self.get_test_file_uri('lib') }} ) - published_diagnostics = self.wait_for_diagnostics(solc, 2) + published_diagnostics = self.wait_for_diagnostics(solc) self.verify_didOpen_with_import_diagnostics(published_diagnostics) def test_textDocument_opening_two_new_files_edit_and_close(self, solc: JsonRpcProcess) -> None: @@ -734,7 +1380,7 @@ class SolidityLSPTestSuite: # {{{ ]) } }) - reports = self.wait_for_diagnostics(solc, 1) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 1, "one publish diagnostics notification") self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") @@ -750,7 +1396,7 @@ class SolidityLSPTestSuite: # {{{ ]) } }) - reports = self.wait_for_diagnostics(solc, 2) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 2, "one publish diagnostics notification") self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") self.expect_equal(len(reports[1]['diagnostics']), 0, "should not contain diagnostics") @@ -769,7 +1415,7 @@ class SolidityLSPTestSuite: # {{{ } ] }) - reports = self.wait_for_diagnostics(solc, 2) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 2, "one publish diagnostics notification") self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") self.expect_equal(len(reports[1]['diagnostics']), 0, "should not contain diagnostics") @@ -779,7 +1425,7 @@ class SolidityLSPTestSuite: # {{{ { 'textDocument': { 'uri': FILE_B_URI }} ) # We only get one diagnostics message since the diagnostics for b.sol was empty. - reports = self.wait_for_diagnostics(solc, 1) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 1, "one publish diagnostics notification") self.expect_diagnostic(reports[0]['diagnostics'][0], 6275, 2, (0, 17)) # a.sol: File B not found self.expect_equal(reports[0]['uri'], FILE_A_URI, "Correct uri") @@ -804,7 +1450,7 @@ class SolidityLSPTestSuite: # {{{ 'import "./lib.sol";\n' } }) - reports = self.wait_for_diagnostics(solc, 2) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 2, '') self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") @@ -818,12 +1464,11 @@ class SolidityLSPTestSuite: # {{{ 'textDocument/didClose', { 'textDocument': { 'uri': FILE_A_URI }} ) - reports = self.wait_for_diagnostics(solc, 1) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 1, '') self.expect_equal(reports[0]['uri'], f'file://{self.project_root_dir}/lib.sol', "") self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") - def test_textDocument_didChange_at_eol(self, solc: JsonRpcProcess) -> None: """ Append at one line and insert a new one below. @@ -839,7 +1484,7 @@ class SolidityLSPTestSuite: # {{{ 'text': self.get_test_file_contents(FILE_NAME) } }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0, "no diagnostics") solc.send_message('textDocument/didChange', { @@ -856,7 +1501,7 @@ class SolidityLSPTestSuite: # {{{ } ] }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") report2 = published_diagnostics[0] self.expect_equal(report2['uri'], FILE_URI, "Correct file URI") @@ -875,238 +1520,13 @@ class SolidityLSPTestSuite: # {{{ } ] }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") report3 = published_diagnostics[0] self.expect_equal(report3['uri'], FILE_URI, "Correct file URI") self.expect_equal(len(report3['diagnostics']), 1, "one diagnostic") self.expect_diagnostic(report3['diagnostics'][0], 4126, 6, (1, 23)) - def test_textDocument_definition(self, solc: JsonRpcProcess) -> None: - self.setup_lsp(solc) - FILE_NAME = 'goto_definition' - FILE_URI = self.get_test_file_uri(FILE_NAME) - LIB_URI = self.get_test_file_uri('lib') - solc.send_message('textDocument/didOpen', { - 'textDocument': { - 'uri': FILE_URI, - 'languageId': 'Solidity', - 'version': 1, - 'text': self.get_test_file_contents(FILE_NAME) - } - }) - published_diagnostics = self.wait_for_diagnostics(solc, 2) - self.expect_equal(len(published_diagnostics), 2, "publish diagnostics for 2 files") - self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0) - self.expect_equal(len(published_diagnostics[1]['diagnostics']), 1) - self.expect_diagnostic(published_diagnostics[1]['diagnostics'][0], 2072, 33, (8, 19)) # unused variable in lib.sol - - # import directive - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(3, 9), # symbol `"./lib.sol"` in `import "./lib.sol"` - expected_uri=LIB_URI, - expected_lineNo=0, - expected_startEndColumns=(0, 0), - description="import directive" - ) - - # type symbol to jump to type defs (error, contract, enum, ...) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(30, 19), # symbol `IA` in `new IA()` - expected_uri=FILE_URI, - expected_lineNo=10, - expected_startEndColumns=(9, 11), - description="type symbol to jump to definition" - ) - - # virtual function lookup? - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(31, 12), # symbol `f`, jumps to interface definition - expected_uri=FILE_URI, - expected_lineNo=7, - expected_startEndColumns=(13, 14), - description="virtual function lookup" - ) - - # using for - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(37, 10), # symbol `add` in `i.add(5)` - expected_uri=FILE_URI, - expected_lineNo=22, - expected_startEndColumns=(13, 16), - description="using for" - ) - - # library - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(43, 15), # symbol `Lib` in `Lib.add(n, 1)` - expected_uri=LIB_URI, - expected_lineNo=22, - expected_startEndColumns=(8, 11), - description="Library symbol from different file" - ) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(43, 19), # symbol `add` in `Lib.add(n, 1)` - expected_uri=LIB_URI, - expected_lineNo=24, - expected_startEndColumns=(13, 16), - description="Library member symbol from different file" - ) - - # enum type symbol and enum values - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(46, 19), # symbol `Color` in function signature's parameter - expected_uri=LIB_URI, - expected_lineNo=13, - expected_startEndColumns=(5, 10), - description="Enum type" - ) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(48, 24), # symbol `Red` in `Color.Red` - expected_uri=LIB_URI, - expected_lineNo=15, - expected_startEndColumns=(4, 7), - description="Enum value" - ) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(48, 24), # symbol `Red` in `Color.Red` - expected_uri=LIB_URI, - expected_lineNo=15, - expected_startEndColumns=(4, 7), - description="Enum value" - ) - - # local variable declarations - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(49, 17), # symbol `e` in `(c == e)` - expected_uri=FILE_URI, - expected_lineNo=48, - expected_startEndColumns=(14, 15), - description="local variable declaration" - ) - - # User defined type - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(58, 8), # symbol `Price` in `Price p ...` - expected_uri=FILE_URI, - expected_lineNo=55, - expected_startEndColumns=(9, 14), - description="User defined type on left hand side" - ) - - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(58, 18), # symbol `Price` in `Price.wrap()` expected_uri=FILE_URI, - expected_uri=FILE_URI, - expected_lineNo=55, - expected_startEndColumns=(9, 14), - description="User defined type on right hand side." - ) - - # struct constructor also properly jumps to the struct's declaration. - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(64, 33), # symbol `RGBColor` right hand side expression. - expected_uri=LIB_URI, - expected_lineNo=38, - expected_startEndColumns=(7, 15), - description="Struct constructor." - ) - - def test_textDocument_definition_imports(self, solc: JsonRpcProcess) -> None: - self.setup_lsp(solc) - FILE_NAME = 'goto_definition_imports' - FILE_URI = self.get_test_file_uri(FILE_NAME) - LIB_URI = self.get_test_file_uri('lib') - solc.send_message('textDocument/didOpen', { - 'textDocument': { - 'uri': FILE_URI, - 'languageId': 'Solidity', - 'version': 1, - 'text': self.get_test_file_contents(FILE_NAME) - } - }) - published_diagnostics = self.wait_for_diagnostics(solc, 2) - self.expect_equal(len(published_diagnostics), 2, "publish diagnostics for 2 files") - self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0) - self.expect_equal(len(published_diagnostics[1]['diagnostics']), 1) - self.expect_diagnostic(published_diagnostics[1]['diagnostics'][0], 2072, 33, (8, 19)) # unused variable in lib.sol - - # import directive: test symbol alias - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(3, 9), # in `Weather` of `import {Weather as Wetter} from "./lib.sol"` - expected_uri=LIB_URI, - expected_lineNo=6, - expected_startEndColumns=(5, 12), - description="goto definition of symbol in symbol alias import directive" - ) - - # import directive: test symbol alias - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(8, 55), # `Wetter` in return type declaration - expected_uri=LIB_URI, - expected_lineNo=6, - expected_startEndColumns=(5, 12), - description="goto definition of symbol in symbol alias import directive" - ) - - # That.Color tests with `That` being the aliased library to be imported. - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(13, 55), # `That` in return type declaration - expected_uri=LIB_URI, - expected_lineNo=13, - expected_startEndColumns=(5, 10), - description="goto definition of symbol in symbol alias import directive" - ) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(15, 8), - expected_uri=LIB_URI, - expected_lineNo=13, - expected_startEndColumns=(5, 10), - description="`That` in LHS variable assignment" - ) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(15, 27), - expected_uri=FILE_URI, - expected_lineNo=4, - expected_startEndColumns=(22, 26), - description="`That` in expression" - ) - def test_textDocument_didChange_empty_file(self, solc: JsonRpcProcess) -> None: """ Starts with an empty file and changes it to look like @@ -1126,7 +1546,7 @@ class SolidityLSPTestSuite: # {{{ 'text': '' } }) - reports = self.wait_for_diagnostics(solc, 1) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 1) report = reports[0] published_diagnostics = report['diagnostics'] @@ -1147,7 +1567,7 @@ class SolidityLSPTestSuite: # {{{ } ] }) - published_diagnostics = self.wait_for_diagnostics(solc, 2) + published_diagnostics = self.wait_for_diagnostics(solc) self.verify_didOpen_with_import_diagnostics(published_diagnostics, 'a_new_file') def test_textDocument_didChange_multi_line(self, solc: JsonRpcProcess) -> None: @@ -1166,7 +1586,7 @@ class SolidityLSPTestSuite: # {{{ 'text': self.get_test_file_contents(FILE_NAME) } }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0, "no diagnostics") solc.send_message('textDocument/didChange', { @@ -1181,7 +1601,7 @@ class SolidityLSPTestSuite: # {{{ } ] }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") report2 = published_diagnostics[0] self.expect_equal(report2['uri'], FILE_URI, "Correct file URI") @@ -1201,7 +1621,7 @@ class SolidityLSPTestSuite: # {{{ } ] }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") report3 = published_diagnostics[0] self.expect_equal(report3['uri'], FILE_URI, "Correct file URI") @@ -1233,6 +1653,9 @@ class SolidityLSPTestSuite: # {{{ # }}} if __name__ == "__main__": + # Turn off user input buffering so we get the input immediately, + # not only after a line break + tty.setcbreak(sys.stdin.fileno()) suite = SolidityLSPTestSuite() exit_code = suite.main() sys.exit(exit_code) From 5111e7543cabb388e75d46a7c8dd0064afc47e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 13 Apr 2022 19:50:11 +0200 Subject: [PATCH 04/21] soltest.sh: Prevent parallel tests from overwriting each other's XML test output --- .circleci/soltest.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/soltest.sh b/.circleci/soltest.sh index 30bd1274c..91325f02e 100755 --- a/.circleci/soltest.sh +++ b/.circleci/soltest.sh @@ -74,7 +74,7 @@ do BOOST_TEST_ARGS_RUN=( "--color_output=no" "--show_progress=yes" - "--logger=JUNIT,error,test_results/$(get_logfile_basename "$run").xml" + "--logger=JUNIT,error,test_results/$(get_logfile_basename "$((CPUs * CIRCLE_NODE_INDEX + run))").xml" "${BOOST_TEST_ARGS[@]}" ) SOLTEST_ARGS=("--evm-version=$EVM" "${SOLTEST_FLAGS[@]}") From 43ff61f1850c94434d85ae0a8062567dd1f7c311 Mon Sep 17 00:00:00 2001 From: aathan Date: Fri, 15 Apr 2022 12:24:48 -0700 Subject: [PATCH 05/21] Update functions.rst --- docs/contracts/functions.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/contracts/functions.rst b/docs/contracts/functions.rst index 8957d0f54..76304aaad 100644 --- a/docs/contracts/functions.rst +++ b/docs/contracts/functions.rst @@ -317,12 +317,13 @@ will consume more gas than the 2300 gas stipend: - Sending Ether .. warning:: - Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``) - but do not define a receive Ether function or a payable fallback function - throw an exception, sending back the Ether (this was different - before Solidity v0.4.0). So if you want your contract to receive Ether, + When Ether is sent directly to a contract (without a function call, i.e. sender uses ``send`` or ``transfer``) + but the receiving contract does not define a receive Ether function or a payable fallback function, + an exception will be thrown, sending back the Ether (this was different + before Solidity v0.4.0). If you want your contract to receive Ether, you have to implement a receive Ether function (using payable fallback functions for receiving Ether is - not recommended, since it would not fail on interface confusions). + not recommended, since the fallback is invoked and would not fail for interface confusions + on the part of the sender). .. warning:: From 048b253a9331f12ee931697844eefc9263d376fa Mon Sep 17 00:00:00 2001 From: a3d4 Date: Thu, 21 Apr 2022 01:44:25 +0200 Subject: [PATCH 06/21] Refix MSVC Debug crash --- libsolutil/Visitor.h | 13 ++++++- libyul/optimiser/StructuralSimplifier.cpp | 43 ++++++++++------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/libsolutil/Visitor.h b/libsolutil/Visitor.h index 497ed2f29..1147f9579 100644 --- a/libsolutil/Visitor.h +++ b/libsolutil/Visitor.h @@ -56,6 +56,17 @@ struct VisitorFallback { template R operator()(T&&) const { retur template<> struct VisitorFallback<> { template void operator()(T&&) const {} }; -template struct GenericVisitor: Visitors... { using Visitors::operator()...; }; +// MSVC. Empty base class optimization does not happen in some scenarios. +// Enforcing it with __declspec(empty_bases) avoids MSVC Debug test crash +// (Run-Time Check Failure #2 - Stack around the variable '....' was corrupted). +// See https://docs.microsoft.com/en-us/cpp/cpp/empty-bases, +// https://developercommunity.visualstudio.com/t/10005513. +#if defined(_MSC_VER) +#define SOLC_EMPTY_BASES __declspec(empty_bases) +#else +#define SOLC_EMPTY_BASES +#endif + +template struct SOLC_EMPTY_BASES GenericVisitor: Visitors... { using Visitors::operator()...; }; template GenericVisitor(Visitors...) -> GenericVisitor; } diff --git a/libyul/optimiser/StructuralSimplifier.cpp b/libyul/optimiser/StructuralSimplifier.cpp index 0b80c42cd..ca8f94239 100644 --- a/libyul/optimiser/StructuralSimplifier.cpp +++ b/libyul/optimiser/StructuralSimplifier.cpp @@ -93,31 +93,26 @@ void StructuralSimplifier::operator()(Block& _block) void StructuralSimplifier::simplify(std::vector& _statements) { - // Explicit local variables ifLambda, switchLambda, forLoopLambda are created to avoid MSVC C++17 Debug test crash - // (Run-Time Check Failure #2 - Stack around the variable '....' was corrupted). - // As soon as the issue is fixed, this workaround can be removed. - auto ifLambda = [&](If& _ifStmt) -> OptionalStatements - { - if (expressionAlwaysTrue(*_ifStmt.condition)) - return {std::move(_ifStmt.body.statements)}; - else if (expressionAlwaysFalse(*_ifStmt.condition)) - return {vector{}}; - return {}; + util::GenericVisitor visitor{ + util::VisitorFallback{}, + [&](If& _ifStmt) -> OptionalStatements { + if (expressionAlwaysTrue(*_ifStmt.condition)) + return {std::move(_ifStmt.body.statements)}; + else if (expressionAlwaysFalse(*_ifStmt.condition)) + return {vector{}}; + return {}; + }, + [&](Switch& _switchStmt) -> OptionalStatements { + if (std::optional const constExprVal = hasLiteralValue(*_switchStmt.expression)) + return replaceConstArgSwitch(_switchStmt, constExprVal.value()); + return {}; + }, + [&](ForLoop& _forLoop) -> OptionalStatements { + if (expressionAlwaysFalse(*_forLoop.condition)) + return {std::move(_forLoop.pre.statements)}; + return {}; + } }; - auto switchLambda = [&](Switch& _switchStmt) -> OptionalStatements - { - if (std::optional const constExprVal = hasLiteralValue(*_switchStmt.expression)) - return replaceConstArgSwitch(_switchStmt, constExprVal.value()); - return {}; - }; - auto forLoopLambda = [&](ForLoop& _forLoop) -> OptionalStatements - { - if (expressionAlwaysFalse(*_forLoop.condition)) - return {std::move(_forLoop.pre.statements)}; - return {}; - }; - - util::GenericVisitor visitor{util::VisitorFallback{}, ifLambda, switchLambda, forLoopLambda}; util::iterateReplacing( _statements, From cb24e5d545fa295e9caf082103107bc327e535ca Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Fri, 29 Apr 2022 16:13:04 +0400 Subject: [PATCH 07/21] fix(parser): error for unexpected token --- libsolidity/parsing/Parser.cpp | 2 +- test/cmdlineTests/recovery_ast_empty_contract/err | 2 +- test/libsolidity/syntaxTests/freeFunctions/free_constructor.sol | 2 +- test/libsolidity/syntaxTests/freeFunctions/free_fallback.sol | 2 +- test/libsolidity/syntaxTests/freeFunctions/free_receive.sol | 2 +- test/libsolidity/syntaxTests/unexpected.sol | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index a1b49a81f..7bf0de045 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -142,7 +142,7 @@ ASTPointer Parser::parse(CharStream& _charStream) expectToken(Token::Semicolon); } else - fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition."); + fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition."); } } solAssert(m_recursionDepth == 0, ""); diff --git a/test/cmdlineTests/recovery_ast_empty_contract/err b/test/cmdlineTests/recovery_ast_empty_contract/err index f7324da63..fed054470 100644 --- a/test/cmdlineTests/recovery_ast_empty_contract/err +++ b/test/cmdlineTests/recovery_ast_empty_contract/err @@ -1,4 +1,4 @@ -Error: Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. +Error: Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. --> recovery_ast_empty_contract/input.sol:3:1: | 3 | c diff --git a/test/libsolidity/syntaxTests/freeFunctions/free_constructor.sol b/test/libsolidity/syntaxTests/freeFunctions/free_constructor.sol index e44356dc3..787898357 100644 --- a/test/libsolidity/syntaxTests/freeFunctions/free_constructor.sol +++ b/test/libsolidity/syntaxTests/freeFunctions/free_constructor.sol @@ -1,3 +1,3 @@ constructor() {} // ---- -// ParserError 7858: (0-11): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. +// ParserError 7858: (0-11): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. diff --git a/test/libsolidity/syntaxTests/freeFunctions/free_fallback.sol b/test/libsolidity/syntaxTests/freeFunctions/free_fallback.sol index e813e58ee..897ed5964 100644 --- a/test/libsolidity/syntaxTests/freeFunctions/free_fallback.sol +++ b/test/libsolidity/syntaxTests/freeFunctions/free_fallback.sol @@ -1,3 +1,3 @@ fallback(){} // ---- -// ParserError 7858: (0-8): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. +// ParserError 7858: (0-8): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. diff --git a/test/libsolidity/syntaxTests/freeFunctions/free_receive.sol b/test/libsolidity/syntaxTests/freeFunctions/free_receive.sol index d1e790d7c..83dfee446 100644 --- a/test/libsolidity/syntaxTests/freeFunctions/free_receive.sol +++ b/test/libsolidity/syntaxTests/freeFunctions/free_receive.sol @@ -1,3 +1,3 @@ receive() {} // ---- -// ParserError 7858: (0-7): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. +// ParserError 7858: (0-7): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. diff --git a/test/libsolidity/syntaxTests/unexpected.sol b/test/libsolidity/syntaxTests/unexpected.sol index 1ffa40c21..c6fae414c 100644 --- a/test/libsolidity/syntaxTests/unexpected.sol +++ b/test/libsolidity/syntaxTests/unexpected.sol @@ -1,3 +1,3 @@ unexpected // ---- -// ParserError 7858: (0-10): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. +// ParserError 7858: (0-10): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. From f6c0edc902f2dabdf67c81435061038bfe3dce98 Mon Sep 17 00:00:00 2001 From: Nobuhiko Otoba <44864310+nobutoba@users.noreply.github.com> Date: Mon, 25 Apr 2022 11:10:23 +0900 Subject: [PATCH 08/21] Add a require statement to the Ballot contract --- docs/examples/voting.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/examples/voting.rst b/docs/examples/voting.rst index 0e8901c3d..a51c01b74 100644 --- a/docs/examples/voting.rst +++ b/docs/examples/voting.rst @@ -109,6 +109,7 @@ of votes. function delegate(address to) external { // assigns reference Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "You have no right to vote"); require(!sender.voted, "You already voted."); require(to != msg.sender, "Self-delegation is disallowed."); From 505fa7763ff71fb5ae1fb2c8309ae064e2eb8885 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 23 Mar 2022 14:57:07 +0100 Subject: [PATCH 09/21] Fix checks for "using for ... global" for libraries. --- Changelog.md | 1 + libsolidity/analysis/TypeChecker.cpp | 49 ++++++++++--------- .../using/library_on_interface.sol | 18 +++++++ .../using/using_global_all_the_types.sol | 38 ++++++++++++++ .../using/global_inside_contract.sol | 1 - .../using/global_library_for_builtin.sol | 9 ++++ .../global_library_for_defined_elsewhere.sol | 6 +++ .../using/global_library_for_interface.sol | 6 +++ .../using/global_library_foreign.sol | 8 +++ .../using/global_library_with_asterisk.sol | 5 ++ .../using/global_with_asterisk.sol | 5 ++ 11 files changed, 122 insertions(+), 24 deletions(-) create mode 100644 test/libsolidity/semanticTests/using/library_on_interface.sol create mode 100644 test/libsolidity/semanticTests/using/using_global_all_the_types.sol create mode 100644 test/libsolidity/syntaxTests/using/global_library_for_builtin.sol create mode 100644 test/libsolidity/syntaxTests/using/global_library_for_defined_elsewhere.sol create mode 100644 test/libsolidity/syntaxTests/using/global_library_for_interface.sol create mode 100644 test/libsolidity/syntaxTests/using/global_library_foreign.sol create mode 100644 test/libsolidity/syntaxTests/using/global_library_with_asterisk.sol create mode 100644 test/libsolidity/syntaxTests/using/global_with_asterisk.sol diff --git a/Changelog.md b/Changelog.md index 26ba5965d..b547c2aaa 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,6 +11,7 @@ Compiler Features: Bugfixes: + * Type Checker: Properly check restrictions of ``using ... global`` in conjunction with libraries. * Assembly-Json: Fix assembly json export to store jump types of operations in `jumpType` field instead of `value`. * TypeChecker: Convert parameters of function type to how they would be called for ``abi.encodeCall``. diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index d83253b29..ed9875fce 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3638,11 +3638,36 @@ void TypeChecker::endVisit(Literal const& _literal) void TypeChecker::endVisit(UsingForDirective const& _usingFor) { + if (_usingFor.global()) + { + if (m_currentContract || !_usingFor.typeName()) + { + solAssert(m_errorReporter.hasErrors()); + return; + } + solAssert(_usingFor.typeName()->annotation().type); + if (Declaration const* typeDefinition = _usingFor.typeName()->annotation().type->typeDefinition()) + { + if (typeDefinition->scope() != m_currentSourceUnit) + m_errorReporter.typeError( + 4117_error, + _usingFor.location(), + "Can only use \"global\" with types defined in the same source unit at file level." + ); + } + else + m_errorReporter.typeError( + 8841_error, + _usingFor.location(), + "Can only use \"global\" with user-defined types." + ); + } + if (!_usingFor.usesBraces()) { solAssert(_usingFor.functionsOrLibrary().size() == 1); ContractDefinition const* library = dynamic_cast( - _usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration + _usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration ); solAssert(library && library->isLibrary()); // No type checking for libraries @@ -3662,28 +3687,6 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) ); solAssert(normalizedType); - if (_usingFor.global()) - { - if (m_currentContract) - solAssert(m_errorReporter.hasErrors()); - if (Declaration const* typeDefinition = _usingFor.typeName()->annotation().type->typeDefinition()) - { - if (typeDefinition->scope() != m_currentSourceUnit) - m_errorReporter.typeError( - 4117_error, - _usingFor.location(), - "Can only use \"global\" with types defined in the same source unit at file level." - ); - } - else - m_errorReporter.typeError( - 8841_error, - _usingFor.location(), - "Can only use \"global\" with user-defined types." - ); - } - - for (ASTPointer const& path: _usingFor.functionsOrLibrary()) { solAssert(path->annotation().referencedDeclaration); diff --git a/test/libsolidity/semanticTests/using/library_on_interface.sol b/test/libsolidity/semanticTests/using/library_on_interface.sol new file mode 100644 index 000000000..ff716dadb --- /dev/null +++ b/test/libsolidity/semanticTests/using/library_on_interface.sol @@ -0,0 +1,18 @@ +using L for I; +interface I { function f() external pure returns (uint); } +library L { + function execute(I i) internal pure returns (uint) { + return i.f(); + } +} +contract C is I { + function x() public view returns (uint) { + I i = this; + return i.execute(); + } + function f() public pure returns (uint) { return 7; } +} +// ==== +// compileViaYul: also +// ---- +// x() -> 7 diff --git a/test/libsolidity/semanticTests/using/using_global_all_the_types.sol b/test/libsolidity/semanticTests/using/using_global_all_the_types.sol new file mode 100644 index 000000000..b4f071fa2 --- /dev/null +++ b/test/libsolidity/semanticTests/using/using_global_all_the_types.sol @@ -0,0 +1,38 @@ +==== Source: A ==== +enum E {A, B} +struct S { uint x; } +type T is uint; +using L for E global; +using L for S global; +using L for T global; +library L { + function f(E e) internal pure returns (uint) { + return uint(e); + } + function f(S memory s) internal pure returns (uint) { + return s.x; + } + function f(T t) internal pure returns (uint) { + return T.unwrap(t); + } +} + +==== Source: B ==== +contract C { + function f() public pure returns (uint a, uint b, uint c) { + E e = E.B; + a = e.f(); + S memory s; + s.x = 7; + b = s.f(); + T t = T.wrap(9); + c = t.f(); + } +} + +import {E, S, T} from "A"; + +// ==== +// compileViaYul: also +// ---- +// f() -> 1, 7, 9 diff --git a/test/libsolidity/syntaxTests/using/global_inside_contract.sol b/test/libsolidity/syntaxTests/using/global_inside_contract.sol index edaeea922..54c01f99c 100644 --- a/test/libsolidity/syntaxTests/using/global_inside_contract.sol +++ b/test/libsolidity/syntaxTests/using/global_inside_contract.sol @@ -4,4 +4,3 @@ contract C { function f(uint) pure{} // ---- // SyntaxError 3367: (17-43): "global" can only be used at file level. -// TypeError 8841: (17-43): Can only use "global" with user-defined types. diff --git a/test/libsolidity/syntaxTests/using/global_library_for_builtin.sol b/test/libsolidity/syntaxTests/using/global_library_for_builtin.sol new file mode 100644 index 000000000..7ff956941 --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_library_for_builtin.sol @@ -0,0 +1,9 @@ +using L for uint global; +using L for uint[] global; +using L for function() returns (uint) global; +library L { +} +// ---- +// TypeError 8841: (0-24): Can only use "global" with user-defined types. +// TypeError 8841: (25-51): Can only use "global" with user-defined types. +// TypeError 8841: (52-97): Can only use "global" with user-defined types. diff --git a/test/libsolidity/syntaxTests/using/global_library_for_defined_elsewhere.sol b/test/libsolidity/syntaxTests/using/global_library_for_defined_elsewhere.sol new file mode 100644 index 000000000..f3f65890f --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_library_for_defined_elsewhere.sol @@ -0,0 +1,6 @@ +using L for L.S global; +library L { + struct S { uint x; } +} +// ---- +// TypeError 4117: (0-23): Can only use "global" with types defined in the same source unit at file level. diff --git a/test/libsolidity/syntaxTests/using/global_library_for_interface.sol b/test/libsolidity/syntaxTests/using/global_library_for_interface.sol new file mode 100644 index 000000000..06a644634 --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_library_for_interface.sol @@ -0,0 +1,6 @@ +interface I {} +using L for I global; +library L { +} +// ---- +// TypeError 8841: (15-36): Can only use "global" with user-defined types. diff --git a/test/libsolidity/syntaxTests/using/global_library_foreign.sol b/test/libsolidity/syntaxTests/using/global_library_foreign.sol new file mode 100644 index 000000000..9eacc2c4b --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_library_foreign.sol @@ -0,0 +1,8 @@ +==== Source: A ==== +struct S { uint x; } +==== Source: B ==== +library L {} +using L for S global; +import {S} from "A"; +// ---- +// TypeError 4117: (B:13-34): Can only use "global" with types defined in the same source unit at file level. diff --git a/test/libsolidity/syntaxTests/using/global_library_with_asterisk.sol b/test/libsolidity/syntaxTests/using/global_library_with_asterisk.sol new file mode 100644 index 000000000..49ac6824d --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_library_with_asterisk.sol @@ -0,0 +1,5 @@ +using L for * global; +library L {} +// ---- +// SyntaxError 8118: (0-21): The type has to be specified explicitly at file level (cannot use '*'). +// SyntaxError 2854: (0-21): Can only globally bind functions to specific types. diff --git a/test/libsolidity/syntaxTests/using/global_with_asterisk.sol b/test/libsolidity/syntaxTests/using/global_with_asterisk.sol new file mode 100644 index 000000000..b5f6d8827 --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_with_asterisk.sol @@ -0,0 +1,5 @@ +using {f} for * global; +function f(uint) pure{} +// ---- +// SyntaxError 8118: (0-23): The type has to be specified explicitly at file level (cannot use '*'). +// SyntaxError 2854: (0-23): Can only globally bind functions to specific types. From 4fd7de36f10eefa5f09c3ebda794da896a72bc9b Mon Sep 17 00:00:00 2001 From: Leo Alt Date: Tue, 3 May 2022 10:43:19 +0200 Subject: [PATCH 10/21] update smt tests z3 4.8.16 --- .circleci/config.yml | 16 ++-- .circleci/osx_install_dependencies.sh | 4 +- CMakeLists.txt | 2 +- scripts/build_emscripten.sh | 4 +- .../model_checker_invariants_all/err | 6 +- .../model_checker_invariants_contract/err | 2 +- .../err | 6 +- .../model_checker_invariants_reentrancy/err | 2 +- .../output.json | 4 +- .../output.json | 12 +-- .../output.json | 4 +- test/libsolidity/SMTCheckerTest.cpp | 12 ++- .../array_members/push_as_lhs_1d.sol | 2 +- .../array_members/push_as_lhs_bytes.sol | 2 +- .../blockchain_state/balance_receive_4.sol | 3 +- .../branches_in_modifiers_2.sol | 2 +- ...same_input_over_state_same_output_fail.sol | 4 +- .../external_calls/call_with_value_1.sol | 2 +- .../external_calls/call_with_value_2.sol | 2 +- .../external_call_from_constructor_3.sol | 2 +- ...nal_hash_known_code_state_reentrancy_2.sol | 4 +- ...h_known_code_state_reentrancy_indirect.sol | 4 +- .../smtCheckerTests/functions/payable_2.sol | 2 +- .../loops/while_nested_continue_fail.sol | 4 +- .../operators/compound_bitwise_or_uint_2.sol | 2 +- .../special/block_vars_chc_internal.sol | 2 +- .../special/tx_vars_reentrancy_1.sol | 2 +- .../types/array_mapping_aliasing_2.sol | 3 +- .../types/array_static_mapping_aliasing_2.sol | 3 +- ...array_struct_array_struct_storage_safe.sol | 1 + .../struct_aliasing_parameter_storage_2.sol | 2 +- .../struct_aliasing_parameter_storage_3.sol | 2 +- .../unchecked/checked_called_by_unchecked.sol | 2 +- .../smtCheckerTests/userTypes/conversion.sol | 87 ------------------- .../userTypes/conversion_1.sol | 35 ++++++++ .../userTypes/conversion_2.sol | 32 +++++++ .../userTypes/conversion_3.sol | 34 ++++++++ .../userTypes/conversion_4.sol | 23 +++++ 38 files changed, 191 insertions(+), 146 deletions(-) delete mode 100644 test/libsolidity/smtCheckerTests/userTypes/conversion.sol create mode 100644 test/libsolidity/smtCheckerTests/userTypes/conversion_1.sol create mode 100644 test/libsolidity/smtCheckerTests/userTypes/conversion_2.sol create mode 100644 test/libsolidity/smtCheckerTests/userTypes/conversion_3.sol create mode 100644 test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol diff --git a/.circleci/config.yml b/.circleci/config.yml index d42021373..2d824c249 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,20 +9,20 @@ version: 2.1 parameters: ubuntu-2004-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004-11 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:9928dc357829e475e8729c62a1c2d495dbb41cb9fe4c4b115a5568be8e1ed69e" + # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004-12 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:5087cc068b48787e89887804e632120b3e65bc5c25086bdf7b152be4fe5fc9ba" ubuntu-2004-clang-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004.clang-11 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:72fb9574c90e8ef908dce4c9dd9788ff4de708b504d970cd9146eed8911c313e" + # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004.clang-12 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:7f53f1bc3d89bdfb0725f604efbbec570d80ffa9b731b47a4842f4e286de0355" ubuntu-1604-clang-ossfuzz-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-16 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:fe54d8e5409827d43edb0dc8ad0d9e4232a675050ceb271c873b73e5ee267938" + # solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-17 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:85b298c763adf5c516238246beb283640eb555e79e7ad6a8e7a6c9ed47ef6324" emscripten-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:emscripten-9 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:d51534dfdd05ece86f69ed7beafd68c15b88606da00a4b7fe2873ccfbd0dce24" + # solbuildpackpusher/solidity-buildpack-deps:emscripten-10 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:bd23831e0025e35a106005b4ac06cb3618f690bfa2833a5881b573c02d35d9fc" evm-version: type: string default: london diff --git a/.circleci/osx_install_dependencies.sh b/.circleci/osx_install_dependencies.sh index cd0637eac..6b328e5a1 100755 --- a/.circleci/osx_install_dependencies.sh +++ b/.circleci/osx_install_dependencies.sh @@ -61,11 +61,11 @@ then ./scripts/install_obsolete_jsoncpp_1_7_4.sh # z3 - z3_version="4.8.14" + z3_version="4.8.16" z3_dir="z3-${z3_version}-x64-osx-10.16" z3_package="${z3_dir}.zip" wget "https://github.com/Z3Prover/z3/releases/download/z3-${z3_version}/${z3_package}" - validate_checksum "$z3_package" 1341671670e0c4e72da80815128a68975ee90816d50ceaf6bd820f06babe2cfd + validate_checksum "$z3_package" 71ed7b6d10c01198187df72cccb8eb6de6d9aa2bf9557b3dd052032524b598a5 unzip "$z3_package" rm "$z3_package" cp "${z3_dir}/bin/libz3.a" /usr/local/lib diff --git a/CMakeLists.txt b/CMakeLists.txt index 759f69a53..a298f4044 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/license.h.in" include/licens include(EthOptions) configure_project(TESTS) -set(LATEST_Z3_VERSION "4.8.14") +set(LATEST_Z3_VERSION "4.8.16") set(MINIMUM_Z3_VERSION "4.8.0") find_package(Z3) if (${Z3_FOUND}) diff --git a/scripts/build_emscripten.sh b/scripts/build_emscripten.sh index 16e9078f3..09d421e5a 100755 --- a/scripts/build_emscripten.sh +++ b/scripts/build_emscripten.sh @@ -34,7 +34,7 @@ else BUILD_DIR="$1" fi -# solbuildpackpusher/solidity-buildpack-deps:emscripten-9 +# solbuildpackpusher/solidity-buildpack-deps:emscripten-10 docker run -v "$(pwd):/root/project" -w /root/project \ - solbuildpackpusher/solidity-buildpack-deps@sha256:d51534dfdd05ece86f69ed7beafd68c15b88606da00a4b7fe2873ccfbd0dce24\ + solbuildpackpusher/solidity-buildpack-deps@sha256:bd23831e0025e35a106005b4ac06cb3618f690bfa2833a5881b573c02d35d9fc\ ./scripts/ci/build_emscripten.sh "$BUILD_DIR" diff --git a/test/cmdlineTests/model_checker_invariants_all/err b/test/cmdlineTests/model_checker_invariants_all/err index 2f028037b..66a76b3c8 100644 --- a/test/cmdlineTests/model_checker_invariants_all/err +++ b/test/cmdlineTests/model_checker_invariants_all/err @@ -5,10 +5,10 @@ Warning: Return value of low-level calls not used. | ^^^^^^^^^^^ Info: Contract invariant(s) for model_checker_invariants_all/input.sol:test: -(x <= 0) +((x <= 0) || true || true) Reentrancy property(ies) for model_checker_invariants_all/input.sol:test: -((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(true || true || ((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) || true) +(true || true || ((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors = 1 -> Assertion failed at assert(x < 10) = 3 -> Assertion failed at assert(x < 10) diff --git a/test/cmdlineTests/model_checker_invariants_contract/err b/test/cmdlineTests/model_checker_invariants_contract/err index a5277bebf..e3058cca5 100644 --- a/test/cmdlineTests/model_checker_invariants_contract/err +++ b/test/cmdlineTests/model_checker_invariants_contract/err @@ -1,2 +1,2 @@ Info: Contract invariant(s) for model_checker_invariants_contract/input.sol:test: -(x <= 0) +((x <= 0) || true) diff --git a/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err b/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err index fad76665e..694f512cd 100644 --- a/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err +++ b/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err @@ -5,10 +5,10 @@ Warning: Return value of low-level calls not used. | ^^^^^^^^^^^ Info: Contract invariant(s) for model_checker_invariants_contract_reentrancy/input.sol:test: -(x <= 0) +((x <= 0) || true || true) Reentrancy property(ies) for model_checker_invariants_contract_reentrancy/input.sol:test: -((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(true || true || ((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) || true) +(true || true || ((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors = 1 -> Assertion failed at assert(x < 10) = 3 -> Assertion failed at assert(x < 10) diff --git a/test/cmdlineTests/model_checker_invariants_reentrancy/err b/test/cmdlineTests/model_checker_invariants_reentrancy/err index a2fd6075a..dc939704b 100644 --- a/test/cmdlineTests/model_checker_invariants_reentrancy/err +++ b/test/cmdlineTests/model_checker_invariants_reentrancy/err @@ -5,6 +5,6 @@ Warning: Return value of low-level calls not used. | ^^^^^^^^^^^ Info: Reentrancy property(ies) for model_checker_invariants_reentrancy/input.sol:test: -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors = 1 -> Assertion failed at assert(x < 10) diff --git a/test/cmdlineTests/standard_model_checker_invariants_contract/output.json b/test/cmdlineTests/standard_model_checker_invariants_contract/output.json index 1cd236baf..385fcb31f 100644 --- a/test/cmdlineTests/standard_model_checker_invariants_contract/output.json +++ b/test/cmdlineTests/standard_model_checker_invariants_contract/output.json @@ -1,7 +1,7 @@ {"errors":[{"component":"general","errorCode":"1180","formattedMessage":"Info: Contract invariant(s) for A:test: -(x <= 0) +((x <= 0) || true) ","message":"Contract invariant(s) for A:test: -(x <= 0) +((x <= 0) || true) ","severity":"info","type":"Info"}],"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json b/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json index f5ced7253..d560ff288 100644 --- a/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json +++ b/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json @@ -5,20 +5,20 @@ | \t\t\t\t\t\t^^^^^^^^^^^ ","message":"Return value of low-level calls not used.","severity":"warning","sourceLocation":{"end":143,"file":"A","start":132},"type":"Warning"},{"component":"general","errorCode":"1180","formattedMessage":"Info: Contract invariant(s) for A:test: -(x <= 0) +((x <= 0) || true || true) Reentrancy property(ies) for A:test: -((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(true || true || ((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) || true) +(true || true || ((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors = 1 -> Assertion failed at assert(x < 10) = 3 -> Assertion failed at assert(x < 10) ","message":"Contract invariant(s) for A:test: -(x <= 0) +((x <= 0) || true || true) Reentrancy property(ies) for A:test: -((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(true || true || ((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) || true) +(true || true || ((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors = 1 -> Assertion failed at assert(x < 10) = 3 -> Assertion failed at assert(x < 10) diff --git a/test/cmdlineTests/standard_model_checker_invariants_reentrancy/output.json b/test/cmdlineTests/standard_model_checker_invariants_reentrancy/output.json index 4158c3ae1..d7cb43429 100644 --- a/test/cmdlineTests/standard_model_checker_invariants_reentrancy/output.json +++ b/test/cmdlineTests/standard_model_checker_invariants_reentrancy/output.json @@ -5,13 +5,13 @@ | \t\t\t\t\t\t^^^^^^^^^^^ ","message":"Return value of low-level calls not used.","severity":"warning","sourceLocation":{"end":143,"file":"A","start":132},"type":"Warning"},{"component":"general","errorCode":"1180","formattedMessage":"Info: Reentrancy property(ies) for A:test: -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors = 1 -> Assertion failed at assert(x < 10) ","message":"Reentrancy property(ies) for A:test: -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors = 1 -> Assertion failed at assert(x < 10) ","severity":"info","type":"Info"}],"sources":{"A":{"id":0}}} diff --git a/test/libsolidity/SMTCheckerTest.cpp b/test/libsolidity/SMTCheckerTest.cpp index 3b6b8e1b8..10084b014 100644 --- a/test/libsolidity/SMTCheckerTest.cpp +++ b/test/libsolidity/SMTCheckerTest.cpp @@ -69,7 +69,17 @@ SMTCheckerTest::SMTCheckerTest(string const& _filename): SyntaxTest(_filename, E else BOOST_THROW_EXCEPTION(runtime_error("Invalid SMT counterexample choice.")); - auto const& ignoreInv = m_reader.stringSetting("SMTIgnoreInv", "no"); + static auto removeInv = [](vector&& errors) { + vector filtered; + for (auto&& e: errors) + if (e.errorId != 1180_error) + filtered.emplace_back(e); + return filtered; + }; + if (m_modelCheckerSettings.invariants.invariants.empty()) + m_expectations = removeInv(move(m_expectations)); + + auto const& ignoreInv = m_reader.stringSetting("SMTIgnoreInv", "yes"); if (ignoreInv == "no") m_modelCheckerSettings.invariants = ModelCheckerInvariants::All(); else if (ignoreInv == "yes") diff --git a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_1d.sol b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_1d.sol index 0d16306b7..b38963b59 100644 --- a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_1d.sol +++ b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_1d.sol @@ -19,4 +19,4 @@ contract C { // SMTEngine: all // SMTIgnoreOS: macos // ---- -// Warning 6328: (199-229): CHC: Assertion violation happens here.\nCounterexample:\nb = [1]\n\nTransaction trace:\nC.constructor()\nState: b = []\nC.g() +// Warning 6328: (199-229): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol index 7bd74f6dc..fd0229d78 100644 --- a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol +++ b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol @@ -19,4 +19,4 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 6328: (265-310): CHC: Assertion violation happens here. +// Warning 6328: (265-310): CHC: Assertion violation happens here.\nCounterexample:\nb = [0x01]\none = 0x01\n\nTransaction trace:\nC.constructor()\nState: b = []\nC.g() diff --git a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol index 4fa420ff1..d72da3efa 100644 --- a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol +++ b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol @@ -16,8 +16,7 @@ contract C { // Warning 4984: (82-85): CHC: Overflow (resulting value larger than 2**256 - 1) might happen here. // Warning 4984: (154-160): CHC: Overflow (resulting value larger than 2**256 - 1) might happen here. // Warning 4984: (212-218): CHC: Overflow (resulting value larger than 2**256 - 1) might happen here. -// Warning 6328: (180-219): CHC: Assertion violation happens here.\nCounterexample:\nc = 1\n\nTransaction trace:\nC.constructor()\nState: c = 0\nC.f(){ msg.value: 11 }\nState: c = 1\nC.inv() -// Info 1180: Contract invariant(s) for :C:\n(((11 * c) + ((- 1) * (:var 1).balances[address(this)])) <= 0)\n +// Warning 6328: (180-219): CHC: Assertion violation happens here. // Warning 2661: (82-85): BMC: Overflow (resulting value larger than 2**256 - 1) happens here. // Warning 2661: (154-160): BMC: Overflow (resulting value larger than 2**256 - 1) happens here. // Warning 2661: (212-218): BMC: Overflow (resulting value larger than 2**256 - 1) happens here. diff --git a/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol b/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol index 98b551c17..f08a26bf7 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol @@ -45,5 +45,5 @@ contract C { // SMTIgnoreOS: macos // ---- // Warning 6328: (255-269): CHC: Assertion violation happens here.\nCounterexample:\nx = 0\n\nTransaction trace:\nC.constructor()\nState: x = 0\nC.test()\n C.reset_if_overflow() -- internal call -// Warning 6328: (502-519): CHC: Assertion violation happens here. +// Warning 6328: (502-519): CHC: Assertion violation happens here.\nCounterexample:\nx = 2\noldx = 1\n\nTransaction trace:\nC.constructor()\nState: x = 0\nC.set(1)\nState: x = 1\nC.test()\n C.reset_if_overflow() -- internal call // Warning 6328: (615-629): CHC: Assertion violation happens here.\nCounterexample:\nx = 1\n\nTransaction trace:\nC.constructor()\nState: x = 0\nC.set(10)\nState: x = 10\nC.test()\n C.reset_if_overflow() -- internal call diff --git a/test/libsolidity/smtCheckerTests/crypto/crypto_functions_same_input_over_state_same_output_fail.sol b/test/libsolidity/smtCheckerTests/crypto/crypto_functions_same_input_over_state_same_output_fail.sol index fd3b55a8c..b89867c39 100644 --- a/test/libsolidity/smtCheckerTests/crypto/crypto_functions_same_input_over_state_same_output_fail.sol +++ b/test/libsolidity/smtCheckerTests/crypto/crypto_functions_same_input_over_state_same_output_fail.sol @@ -48,12 +48,10 @@ contract C { // Warning 1218: (693-712): CHC: Error trying to invoke SMT solver. // Warning 1218: (716-735): CHC: Error trying to invoke SMT solver. // Warning 1218: (739-758): CHC: Error trying to invoke SMT solver. -// Warning 1218: (762-781): CHC: Error trying to invoke SMT solver. // Warning 6328: (693-712): CHC: Assertion violation might happen here. // Warning 6328: (716-735): CHC: Assertion violation might happen here. // Warning 6328: (739-758): CHC: Assertion violation might happen here. -// Warning 6328: (762-781): CHC: Assertion violation might happen here. +// Warning 6328: (762-781): CHC: Assertion violation happens here. // Warning 4661: (693-712): BMC: Assertion violation happens here. // Warning 4661: (716-735): BMC: Assertion violation happens here. // Warning 4661: (739-758): BMC: Assertion violation happens here. -// Warning 4661: (762-781): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol b/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol index 813185adf..f9293f693 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol @@ -12,5 +12,5 @@ contract C { // ---- // Warning 9302: (96-117): Return value of low-level calls not used. // Warning 6328: (121-156): CHC: Assertion violation might happen here. -// Warning 6328: (175-211): CHC: Assertion violation happens here. +// Warning 6328: (175-211): CHC: Assertion violation happens here.\nCounterexample:\n\ni = 0x0\n\nTransaction trace:\nC.constructor()\nC.g(0x0)\n i.call{value: 10}("") -- untrusted external call // Warning 4661: (121-156): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/call_with_value_2.sol b/test/libsolidity/smtCheckerTests/external_calls/call_with_value_2.sol index c38658080..968095bb2 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/call_with_value_2.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/call_with_value_2.sol @@ -11,5 +11,5 @@ contract C { // ---- // Warning 9302: (96-116): Return value of low-level calls not used. // Warning 6328: (120-156): CHC: Assertion violation might happen here. -// Warning 6328: (175-210): CHC: Assertion violation happens here.\nCounterexample:\n\ni = 0x0\n\nTransaction trace:\nC.constructor()\nC.g(0x0)\n i.call{value: 0}("") -- untrusted external call +// Warning 6328: (175-210): CHC: Assertion violation happens here. // Warning 4661: (120-156): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol b/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol index eb5cd97dd..9d27d6d3b 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol @@ -20,4 +20,4 @@ contract C { // SMTEngine: all // ---- // Warning 6328: (69-85): CHC: Assertion violation happens here.\nCounterexample:\n\n_x = 100\n = 0\n\nTransaction trace:\nState.constructor()\nState.f(100) -// Warning 6328: (203-217): CHC: Assertion violation happens here.\nCounterexample:\ns = 0, z = 3\n\nTransaction trace:\nC.constructor()\nState: s = 0, z = 3\nC.f() +// Warning 6328: (203-217): CHC: Assertion violation happens here.\nCounterexample:\ns = 0, z = 0\n\nTransaction trace:\nC.constructor()\nState: s = 0, z = 0\nC.f() diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_2.sol b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_2.sol index be9f698e1..37509269b 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_2.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_2.sol @@ -42,4 +42,6 @@ contract C { // SMTIgnoreOS: macos // ---- // Warning 2018: (33-88): Function state mutability can be restricted to view -// Warning 6328: (367-381): CHC: Assertion violation happens here. +// Warning 1218: (367-381): CHC: Error trying to invoke SMT solver. +// Warning 6328: (367-381): CHC: Assertion violation might happen here. +// Warning 4661: (367-381): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol index ea9764e17..bd779b56a 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol @@ -44,7 +44,5 @@ contract C { // SMTIgnoreCex: yes // SMTIgnoreOS: macos // ---- -// Warning 1218: (437-463): CHC: Error trying to invoke SMT solver. // Warning 6328: (419-433): CHC: Assertion violation happens here. -// Warning 6328: (437-463): CHC: Assertion violation might happen here. -// Warning 4661: (437-463): BMC: Assertion violation happens here. +// Warning 6328: (437-463): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/functions/payable_2.sol b/test/libsolidity/smtCheckerTests/functions/payable_2.sol index 98717b133..04833ae02 100644 --- a/test/libsolidity/smtCheckerTests/functions/payable_2.sol +++ b/test/libsolidity/smtCheckerTests/functions/payable_2.sol @@ -24,4 +24,4 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 6328: (261-283): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.g2(){ msg.value: 35 }\n C.i() -- internal call +// Warning 6328: (261-283): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.g2(){ msg.value: 1 }\n C.i() -- internal call diff --git a/test/libsolidity/smtCheckerTests/loops/while_nested_continue_fail.sol b/test/libsolidity/smtCheckerTests/loops/while_nested_continue_fail.sol index fe43db0de..180d85adc 100644 --- a/test/libsolidity/smtCheckerTests/loops/while_nested_continue_fail.sol +++ b/test/libsolidity/smtCheckerTests/loops/while_nested_continue_fail.sol @@ -27,7 +27,5 @@ contract C // ==== // SMTEngine: all // ---- -// Warning 1218: (290-305): CHC: Error trying to invoke SMT solver. -// Warning 6328: (290-305): CHC: Assertion violation might happen here. +// Warning 6328: (290-305): CHC: Assertion violation happens here.\nCounterexample:\n\nx = 0\ny = 15\nb = false\nc = false\n\nTransaction trace:\nC.constructor()\nC.f(0, 0, false, false) // Warning 6328: (329-344): CHC: Assertion violation happens here.\nCounterexample:\n\nx = 15\ny = 0\nb = true\nc = false\n\nTransaction trace:\nC.constructor()\nC.f(0, 0, true, false) -// Warning 4661: (290-305): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/operators/compound_bitwise_or_uint_2.sol b/test/libsolidity/smtCheckerTests/operators/compound_bitwise_or_uint_2.sol index 9b27a277b..edd36047c 100644 --- a/test/libsolidity/smtCheckerTests/operators/compound_bitwise_or_uint_2.sol +++ b/test/libsolidity/smtCheckerTests/operators/compound_bitwise_or_uint_2.sol @@ -19,4 +19,4 @@ contract C { // ==== // SMTEngine: all // ---- -// Info 1180: Contract invariant(s) for :C:\n!(s.x.length <= 2)\n +// Warning 6368: (196-202): CHC: Out of bounds access might happen here. diff --git a/test/libsolidity/smtCheckerTests/special/block_vars_chc_internal.sol b/test/libsolidity/smtCheckerTests/special/block_vars_chc_internal.sol index 2ebf4d326..6e4f5a620 100644 --- a/test/libsolidity/smtCheckerTests/special/block_vars_chc_internal.sol +++ b/test/libsolidity/smtCheckerTests/special/block_vars_chc_internal.sol @@ -32,4 +32,4 @@ contract C { // ==== // SMTEngine: chc // ---- -// Warning 6328: (770-799): CHC: Assertion violation happens here.\nCounterexample:\ncoin = 0x0, dif = 0, gas = 0, number = 0, timestamp = 0\n\nTransaction trace:\nC.constructor()\nState: coin = 0x0, dif = 0, gas = 0, number = 0, timestamp = 0\nC.f(){ block.coinbase: 0x0, block.difficulty: 0, block.gaslimit: 0, block.number: 0, block.timestamp: 0 }\n C.g() -- internal call +// Warning 6328: (770-799): CHC: Assertion violation happens here.\nCounterexample:\ncoin = 0x1e28, dif = 0, gas = 0, number = 0, timestamp = 0\n\nTransaction trace:\nC.constructor()\nState: coin = 0x0, dif = 0, gas = 0, number = 0, timestamp = 0\nC.f(){ block.coinbase: 0x1e28, block.difficulty: 0, block.gaslimit: 0, block.number: 0, block.timestamp: 0 }\n C.g() -- internal call diff --git a/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol b/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol index 7729cebb9..f1fed73c0 100644 --- a/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol +++ b/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol @@ -13,4 +13,4 @@ contract C { // SMTEngine: all // SMTIgnoreOS: macos // ---- -// Warning 6328: (135-169): CHC: Assertion violation happens here.\nCounterexample:\n\n_i = 0\nx = 2997\n\nTransaction trace:\nC.constructor()\nC.g(0){ msg.value: 2803 }\n _i.f() -- untrusted external call, synthesized as:\n C.g(0){ msg.value: 2446 } -- reentrant call\n _i.f() -- untrusted external call +// Warning 6328: (135-169): CHC: Assertion violation happens here.\nCounterexample:\n\n_i = 0\nx = 9726\n\nTransaction trace:\nC.constructor()\nC.g(0){ msg.value: 2070 }\n _i.f() -- untrusted external call, synthesized as:\n C.g(0){ msg.value: 0 } -- reentrant call\n _i.f() -- untrusted external call diff --git a/test/libsolidity/smtCheckerTests/types/array_mapping_aliasing_2.sol b/test/libsolidity/smtCheckerTests/types/array_mapping_aliasing_2.sol index dd0d8e004..cd1d77d8d 100644 --- a/test/libsolidity/smtCheckerTests/types/array_mapping_aliasing_2.sol +++ b/test/libsolidity/smtCheckerTests/types/array_mapping_aliasing_2.sol @@ -36,11 +36,12 @@ contract C // SMTIgnoreCex: yes // ---- // Warning 6368: (439-453): CHC: Out of bounds access happens here. +// Warning 6368: (465-480): CHC: Out of bounds access might happen here. // Warning 6368: (492-508): CHC: Out of bounds access happens here. // Warning 6368: (492-511): CHC: Out of bounds access happens here. // Warning 6368: (622-636): CHC: Out of bounds access happens here. +// Warning 6368: (737-752): CHC: Out of bounds access might happen here. // Warning 6368: (850-866): CHC: Out of bounds access happens here. // Warning 6368: (850-869): CHC: Out of bounds access happens here. // Warning 6328: (936-956): CHC: Assertion violation happens here. // Warning 6368: (1029-1043): CHC: Out of bounds access might happen here. -// Info 1180: Contract invariant(s) for :C:\n!(severalMaps8.length <= 0)\n diff --git a/test/libsolidity/smtCheckerTests/types/array_static_mapping_aliasing_2.sol b/test/libsolidity/smtCheckerTests/types/array_static_mapping_aliasing_2.sol index f9aa3a650..ca1a58018 100644 --- a/test/libsolidity/smtCheckerTests/types/array_static_mapping_aliasing_2.sol +++ b/test/libsolidity/smtCheckerTests/types/array_static_mapping_aliasing_2.sol @@ -30,7 +30,8 @@ contract C // SMTEngine: all // SMTIgnoreCex: yes // ---- +// Warning 6368: (340-355): CHC: Out of bounds access might happen here. +// Warning 6368: (612-627): CHC: Out of bounds access might happen here. // Warning 6328: (860-880): CHC: Assertion violation happens here. // Warning 6368: (936-952): CHC: Out of bounds access might happen here. // Warning 6368: (936-955): CHC: Out of bounds access might happen here. -// Info 1180: Contract invariant(s) for :C:\n!(severalMaps8.length <= 1)\n diff --git a/test/libsolidity/smtCheckerTests/types/struct/array_struct_array_struct_storage_safe.sol b/test/libsolidity/smtCheckerTests/types/struct/array_struct_array_struct_storage_safe.sol index 109fee7dc..67f56d8c6 100644 --- a/test/libsolidity/smtCheckerTests/types/struct/array_struct_array_struct_storage_safe.sol +++ b/test/libsolidity/smtCheckerTests/types/struct/array_struct_array_struct_storage_safe.sol @@ -53,3 +53,4 @@ contract C { // ==== // SMTEngine: all // ---- +// Warning 6368: (212-217): CHC: Out of bounds access might happen here. diff --git a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol index 698262382..9d4623ae0 100644 --- a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol +++ b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol @@ -20,4 +20,4 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 6328: (289-322): CHC: Assertion violation happens here.\nCounterexample:\ns = {innerM, sum: 11}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: s = {innerM, sum: 11}\nC.g(){ msg.sender: 0x0985 } +// Warning 6328: (289-322): CHC: Assertion violation happens here.\nCounterexample:\ns = {innerM, sum: 10}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: s = {innerM, sum: 10}\nC.g(){ msg.sender: 0x0985 } diff --git a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol index fcf30555e..8779cd095 100644 --- a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol +++ b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol @@ -26,4 +26,4 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 6328: (307-327): CHC: Assertion violation happens here.\nCounterexample:\nt = {x: 11, s: {innerM, sum: 21239}}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: t = {x: 11, s: {innerM, sum: 21239}}\nC.g() +// Warning 6328: (307-327): CHC: Assertion violation happens here.\nCounterexample:\nt = {x: 10, s: {innerM, sum: 21239}}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: t = {x: 10, s: {innerM, sum: 21239}}\nC.g() diff --git a/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol b/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol index cb795f9c0..d3589fed5 100644 --- a/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol +++ b/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol @@ -10,4 +10,4 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 4984: (96-101): CHC: Overflow (resulting value larger than 65535) happens here.\nCounterexample:\n\na = 1\nb = 65535\n = 0\n\nTransaction trace:\nC.constructor()\nC.add(1, 65535) +// Warning 4984: (96-101): CHC: Overflow (resulting value larger than 65535) happens here.\nCounterexample:\n\na = 65535\nb = 1\n = 0\n\nTransaction trace:\nC.constructor()\nC.add(65535, 1) diff --git a/test/libsolidity/smtCheckerTests/userTypes/conversion.sol b/test/libsolidity/smtCheckerTests/userTypes/conversion.sol deleted file mode 100644 index cad0233a7..000000000 --- a/test/libsolidity/smtCheckerTests/userTypes/conversion.sol +++ /dev/null @@ -1,87 +0,0 @@ -pragma abicoder v2; - -type MyUInt8 is uint8; -type MyInt8 is int8; -type MyUInt16 is uint16; - -contract C { - function f(uint a) internal pure returns(MyUInt8) { - return MyUInt8.wrap(uint8(a)); - } - function g(uint a) internal pure returns(MyInt8) { - return MyInt8.wrap(int8(int(a))); - } - function h(MyUInt8 a) internal pure returns (MyInt8) { - return MyInt8.wrap(int8(MyUInt8.unwrap(a))); - } - function i(MyUInt8 a) internal pure returns(MyUInt16) { - return MyUInt16.wrap(MyUInt8.unwrap(a)); - } - function j(MyUInt8 a) internal pure returns (uint) { - return MyUInt8.unwrap(a); - } - function k(MyUInt8 a) internal pure returns (MyUInt16) { - return MyUInt16.wrap(MyUInt8.unwrap(a)); - } - function m(MyUInt16 a) internal pure returns (MyUInt8) { - return MyUInt8.wrap(uint8(MyUInt16.unwrap(a))); - } - - function p() public pure { - assert(MyUInt8.unwrap(f(1)) == 1); - assert(MyUInt8.unwrap(f(2)) == 2); - assert(MyUInt8.unwrap(f(257)) == 1); - assert(MyUInt8.unwrap(f(257)) == 257); // should fail - } - - function q() public pure { - assert(MyInt8.unwrap(g(1)) == 1); - assert(MyInt8.unwrap(g(2)) == 2); - assert(MyInt8.unwrap(g(255)) == -1); - assert(MyInt8.unwrap(g(257)) == 1); - assert(MyInt8.unwrap(g(257)) == -1); // should fail - } - - function r() public pure { - assert(MyInt8.unwrap(h(MyUInt8.wrap(1))) == 1); - assert(MyInt8.unwrap(h(MyUInt8.wrap(2))) == 2); - assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == -1); - assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == 1); // should fail - } - - function s() public pure { - assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 250); - assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 0); // should fail - } - - function t() public pure { - assert(j(MyUInt8.wrap(1)) == 1); - assert(j(MyUInt8.wrap(2)) == 2); - assert(j(MyUInt8.wrap(255)) == 0xff); - assert(j(MyUInt8.wrap(255)) == 1); // should fail - } - - function v() public pure { - assert(MyUInt16.unwrap(k(MyUInt8.wrap(1))) == 1); - assert(MyUInt16.unwrap(k(MyUInt8.wrap(2))) == 2); - assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 0xff); - assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 1); // should fail - } - - function w() public pure { - assert(MyUInt8.unwrap(m(MyUInt16.wrap(1))) == 1); - assert(MyUInt8.unwrap(m(MyUInt16.wrap(2))) == 2); - assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 0xff); - assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 1); // should fail - } -} -// ==== -// SMTEngine: all -// ---- -// Warning 6328: (937-974): CHC: Assertion violation happens here. -// Warning 6328: (1174-1209): CHC: Assertion violation happens here. -// Warning 6328: (1413-1461): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.r()\n C.h(1) -- internal call\n C.h(2) -- internal call\n C.h(255) -- internal call\n C.h(255) -- internal call -// Warning 6328: (1568-1618): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.s()\n C.i(250) -- internal call\n C.i(250) -- internal call -// Warning 6328: (1779-1812): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.t()\n C.j(1) -- internal call\n C.j(2) -- internal call\n C.j(255) -- internal call\n C.j(255) -- internal call -// Warning 6328: (2024-2074): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.v()\n C.k(1) -- internal call\n C.k(2) -- internal call\n C.k(255) -- internal call\n C.k(255) -- internal call -// Warning 6328: (2286-2336): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/userTypes/conversion_1.sol b/test/libsolidity/smtCheckerTests/userTypes/conversion_1.sol new file mode 100644 index 000000000..fa56e11ab --- /dev/null +++ b/test/libsolidity/smtCheckerTests/userTypes/conversion_1.sol @@ -0,0 +1,35 @@ +pragma abicoder v2; + +type MyUInt8 is uint8; +type MyInt8 is int8; +type MyUInt16 is uint16; + +contract C { + function f(uint a) internal pure returns(MyUInt8) { + return MyUInt8.wrap(uint8(a)); + } + function g(uint a) internal pure returns(MyInt8) { + return MyInt8.wrap(int8(int(a))); + } + + function p() public pure { + assert(MyUInt8.unwrap(f(1)) == 1); + assert(MyUInt8.unwrap(f(2)) == 2); + assert(MyUInt8.unwrap(f(257)) == 1); + assert(MyUInt8.unwrap(f(257)) == 257); // should fail + } + + function q() public pure { + assert(MyInt8.unwrap(g(1)) == 1); + assert(MyInt8.unwrap(g(2)) == 2); + assert(MyInt8.unwrap(g(255)) == -1); + assert(MyInt8.unwrap(g(257)) == 1); + assert(MyInt8.unwrap(g(257)) == -1); // should fail + } +} +// ==== +// SMTEngine: all +// ---- +// Warning 6328: (428-465): CHC: Assertion violation happens here. +// Warning 6328: (665-700): CHC: Assertion violation happens here. +// Info 1180: Contract invariant(s) for :C:\n(true || true || true || true || true)\nReentrancy property(ies) for :C:\n(true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true || true || true)\n(true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true || true)\n(true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true)\n(true || true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true)\n(true || true || true || true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true)\n = 0 -> no errors\n = 1 -> Assertion failed at assert(MyUInt8.unwrap(f(1)) == 1)\n = 2 -> Assertion failed at assert(MyUInt8.unwrap(f(2)) == 2)\n = 3 -> Assertion failed at assert(MyUInt8.unwrap(f(257)) == 1)\n = 4 -> Assertion failed at assert(MyUInt8.unwrap(f(257)) == 257)\n = 6 -> Assertion failed at assert(MyInt8.unwrap(g(1)) == 1)\n = 7 -> Assertion failed at assert(MyInt8.unwrap(g(2)) == 2)\n = 8 -> Assertion failed at assert(MyInt8.unwrap(g(255)) == -1)\n = 9 -> Assertion failed at assert(MyInt8.unwrap(g(257)) == 1)\n = 10 -> Assertion failed at assert(MyInt8.unwrap(g(257)) == -1)\n diff --git a/test/libsolidity/smtCheckerTests/userTypes/conversion_2.sol b/test/libsolidity/smtCheckerTests/userTypes/conversion_2.sol new file mode 100644 index 000000000..9b4f72d33 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/userTypes/conversion_2.sol @@ -0,0 +1,32 @@ +pragma abicoder v2; + +type MyUInt8 is uint8; +type MyInt8 is int8; +type MyUInt16 is uint16; + +contract C { + function h(MyUInt8 a) internal pure returns (MyInt8) { + return MyInt8.wrap(int8(MyUInt8.unwrap(a))); + } + function i(MyUInt8 a) internal pure returns(MyUInt16) { + return MyUInt16.wrap(MyUInt8.unwrap(a)); + } + + function r() public pure { + assert(MyInt8.unwrap(h(MyUInt8.wrap(1))) == 1); + assert(MyInt8.unwrap(h(MyUInt8.wrap(2))) == 2); + assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == -1); + assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == 1); // should fail + } + + function s() public pure { + assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 250); + assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 0); // should fail + } +} +// ==== +// SMTEngine: all +// ---- +// Warning 6328: (497-545): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.r()\n C.h(1) -- internal call\n C.h(2) -- internal call\n C.h(255) -- internal call\n C.h(255) -- internal call +// Warning 6328: (652-702): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.s()\n C.i(250) -- internal call\n C.i(250) -- internal call +// Info 1180: Contract invariant(s) for :C:\n(true || true || true || true || true)\nReentrancy property(ies) for :C:\n((( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true || true || true || true)\n(true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true || true || true)\n(true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true)\n(true || true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true)\n = 0 -> no errors\n = 1 -> Assertion failed at assert(MyInt8.unwrap(h(MyUInt8.wrap(1))) == 1)\n = 2 -> Assertion failed at assert(MyInt8.unwrap(h(MyUInt8.wrap(2))) == 2)\n = 3 -> Assertion failed at assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == -1)\n = 4 -> Assertion failed at assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == 1)\n = 6 -> Assertion failed at assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 250)\n = 7 -> Assertion failed at assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 0)\n diff --git a/test/libsolidity/smtCheckerTests/userTypes/conversion_3.sol b/test/libsolidity/smtCheckerTests/userTypes/conversion_3.sol new file mode 100644 index 000000000..ea8e9804b --- /dev/null +++ b/test/libsolidity/smtCheckerTests/userTypes/conversion_3.sol @@ -0,0 +1,34 @@ +pragma abicoder v2; + +type MyUInt8 is uint8; +type MyInt8 is int8; +type MyUInt16 is uint16; + +contract C { + function j(MyUInt8 a) internal pure returns (uint) { + return MyUInt8.unwrap(a); + } + function k(MyUInt8 a) internal pure returns (MyUInt16) { + return MyUInt16.wrap(MyUInt8.unwrap(a)); + } + + function t() public pure { + assert(j(MyUInt8.wrap(1)) == 1); + assert(j(MyUInt8.wrap(2)) == 2); + assert(j(MyUInt8.wrap(255)) == 0xff); + assert(j(MyUInt8.wrap(255)) == 1); // should fail + } + + function v() public pure { + assert(MyUInt16.unwrap(k(MyUInt8.wrap(1))) == 1); + assert(MyUInt16.unwrap(k(MyUInt8.wrap(2))) == 2); + assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 0xff); + assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 1); // should fail + } +} +// ==== +// SMTEngine: all +// ---- +// Warning 6328: (434-467): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.t()\n C.j(1) -- internal call\n C.j(2) -- internal call\n C.j(255) -- internal call\n C.j(255) -- internal call +// Warning 6328: (679-729): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.v()\n C.k(1) -- internal call\n C.k(2) -- internal call\n C.k(255) -- internal call\n C.k(255) -- internal call +// Info 1180: Contract invariant(s) for :C:\n(true || true || true || true || true)\nReentrancy property(ies) for :C:\n((( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true || true || true || true)\n(true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true)\n(true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true)\n(true || true || true || true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true)\n(true || true || true || true || true || true || true || true || (( = 0) && ((:var 0) = (:var 1))))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(j(MyUInt8.wrap(1)) == 1)\n = 2 -> Assertion failed at assert(j(MyUInt8.wrap(2)) == 2)\n = 3 -> Assertion failed at assert(j(MyUInt8.wrap(255)) == 0xff)\n = 4 -> Assertion failed at assert(j(MyUInt8.wrap(255)) == 1)\n = 6 -> Assertion failed at assert(MyUInt16.unwrap(k(MyUInt8.wrap(1))) == 1)\n = 7 -> Assertion failed at assert(MyUInt16.unwrap(k(MyUInt8.wrap(2))) == 2)\n = 8 -> Assertion failed at assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 0xff)\n = 9 -> Assertion failed at assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 1)\n diff --git a/test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol b/test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol new file mode 100644 index 000000000..ced7e37c4 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol @@ -0,0 +1,23 @@ +pragma abicoder v2; + +type MyUInt8 is uint8; +type MyInt8 is int8; +type MyUInt16 is uint16; + +contract C { + function m(MyUInt16 a) internal pure returns (MyUInt8) { + return MyUInt8.wrap(uint8(MyUInt16.unwrap(a))); + } + + function w() public pure { + assert(MyUInt8.unwrap(m(MyUInt16.wrap(1))) == 1); + assert(MyUInt8.unwrap(m(MyUInt16.wrap(2))) == 2); + assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 0xff); + assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 1); // should fail + } +} +// ==== +// SMTEngine: all +// ---- +// Warning 6328: (407-457): CHC: Assertion violation happens here. +// Info 1180: Contract invariant(s) for :C:\n(true || true || true)\nReentrancy property(ies) for :C:\n((( = 0) && ((:var 0) = (:var 1))) || true || true || true || true)\n(true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true)\n = 0 -> no errors\n = 1 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(1))) == 1)\n = 2 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(2))) == 2)\n = 3 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 0xff)\n = 4 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 1)\n From cba3d18f6607bf1dac45fe2eb9fa1feb4871e604 Mon Sep 17 00:00:00 2001 From: Leo Alt Date: Wed, 4 May 2022 16:29:50 +0200 Subject: [PATCH 11/21] adjust for osx nondeterminism --- .../model_checker_invariants_all/err | 8 ++-- .../model_checker_invariants_all/input.sol | 5 +-- .../err | 8 ++-- .../input.sol | 5 +-- .../input.json | 5 +-- .../output.json | 40 +++++++++++-------- .../abi/abi_encode_with_selector_hash.sol | 16 ++++---- .../array_members/push_as_lhs_bytes.sol | 3 +- .../array_members/push_as_lhs_bytes_2d.sol | 1 + .../functions/getters/array_1.sol | 3 +- .../overflow/unsigned_mul_overflow.sol | 3 +- .../try_catch/try_nested_2.sol | 3 +- .../struct_aliasing_parameter_storage_3.sol | 3 +- .../unchecked/checked_called_by_unchecked.sol | 3 +- 14 files changed, 54 insertions(+), 52 deletions(-) diff --git a/test/cmdlineTests/model_checker_invariants_all/err b/test/cmdlineTests/model_checker_invariants_all/err index 66a76b3c8..65228e880 100644 --- a/test/cmdlineTests/model_checker_invariants_all/err +++ b/test/cmdlineTests/model_checker_invariants_all/err @@ -5,10 +5,8 @@ Warning: Return value of low-level calls not used. | ^^^^^^^^^^^ Info: Contract invariant(s) for model_checker_invariants_all/input.sol:test: -((x <= 0) || true || true) +((x <= 0) || true) Reentrancy property(ies) for model_checker_invariants_all/input.sol:test: -(true || true || ((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) || true) -(true || true || ((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) +(((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors - = 1 -> Assertion failed at assert(x < 10) - = 3 -> Assertion failed at assert(x < 10) + = 1 -> Assertion failed at assert(x == 0) diff --git a/test/cmdlineTests/model_checker_invariants_all/input.sol b/test/cmdlineTests/model_checker_invariants_all/input.sol index f8601cf4f..8b90cb3c1 100644 --- a/test/cmdlineTests/model_checker_invariants_all/input.sol +++ b/test/cmdlineTests/model_checker_invariants_all/input.sol @@ -4,9 +4,6 @@ contract test { uint x; function f(address _a) public { _a.call(""); - assert(x < 10); + assert(x == 0); } - function g() public view { - assert(x < 10); - } } \ No newline at end of file diff --git a/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err b/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err index 694f512cd..855521836 100644 --- a/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err +++ b/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err @@ -5,10 +5,8 @@ Warning: Return value of low-level calls not used. | ^^^^^^^^^^^ Info: Contract invariant(s) for model_checker_invariants_contract_reentrancy/input.sol:test: -((x <= 0) || true || true) +((x <= 0) || true) Reentrancy property(ies) for model_checker_invariants_contract_reentrancy/input.sol:test: -(true || true || ((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) || true) -(true || true || ((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) +(((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors - = 1 -> Assertion failed at assert(x < 10) - = 3 -> Assertion failed at assert(x < 10) + = 1 -> Assertion failed at assert(x == 0) diff --git a/test/cmdlineTests/model_checker_invariants_contract_reentrancy/input.sol b/test/cmdlineTests/model_checker_invariants_contract_reentrancy/input.sol index f8601cf4f..8b90cb3c1 100644 --- a/test/cmdlineTests/model_checker_invariants_contract_reentrancy/input.sol +++ b/test/cmdlineTests/model_checker_invariants_contract_reentrancy/input.sol @@ -4,9 +4,6 @@ contract test { uint x; function f(address _a) public { _a.call(""); - assert(x < 10); + assert(x == 0); } - function g() public view { - assert(x < 10); - } } \ No newline at end of file diff --git a/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/input.json b/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/input.json index 56139e6a5..38f658653 100644 --- a/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/input.json +++ b/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/input.json @@ -8,10 +8,7 @@ uint x; function f(address _a) public { _a.call(\"\"); - assert(x < 10); - } - function g() public view { - assert(x < 10); + assert(x == 10); } }" } diff --git a/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json b/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json index d560ff288..64d63c46c 100644 --- a/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json +++ b/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json @@ -4,22 +4,28 @@ 7 | \t\t\t\t\t\t_a.call(\"\"); | \t\t\t\t\t\t^^^^^^^^^^^ -","message":"Return value of low-level calls not used.","severity":"warning","sourceLocation":{"end":143,"file":"A","start":132},"type":"Warning"},{"component":"general","errorCode":"1180","formattedMessage":"Info: Contract invariant(s) for A:test: -((x <= 0) || true || true) -Reentrancy property(ies) for A:test: -(true || true || ((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) || true) -(true || true || ((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) - = 0 -> no errors - = 1 -> Assertion failed at assert(x < 10) - = 3 -> Assertion failed at assert(x < 10) +","message":"Return value of low-level calls not used.","severity":"warning","sourceLocation":{"end":143,"file":"A","start":132},"type":"Warning"},{"component":"general","errorCode":"6328","formattedMessage":"Warning: CHC: Assertion violation happens here. +Counterexample: +x = 0 +_a = 0x0 +Transaction trace: +test.constructor() +State: x = 0 +test.f(0x0) + _a.call(\"\") -- untrusted external call + --> A:8:7: + | +8 | \t\t\t\t\t\tassert(x == 10); + | \t\t\t\t\t\t^^^^^^^^^^^^^^^ -","message":"Contract invariant(s) for A:test: -((x <= 0) || true || true) -Reentrancy property(ies) for A:test: -(true || true || ((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) || true) -(true || true || ((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) - = 0 -> no errors - = 1 -> Assertion failed at assert(x < 10) - = 3 -> Assertion failed at assert(x < 10) -","severity":"info","type":"Info"}],"sources":{"A":{"id":0}}} +","message":"CHC: Assertion violation happens here. +Counterexample: +x = 0 +_a = 0x0 + +Transaction trace: +test.constructor() +State: x = 0 +test.f(0x0) + _a.call(\"\") -- untrusted external call","severity":"warning","sourceLocation":{"end":166,"file":"A","start":151},"type":"Warning"}],"sources":{"A":{"id":0}}} diff --git a/test/libsolidity/smtCheckerTests/abi/abi_encode_with_selector_hash.sol b/test/libsolidity/smtCheckerTests/abi/abi_encode_with_selector_hash.sol index ca1b6b40a..f60d5f52b 100644 --- a/test/libsolidity/smtCheckerTests/abi/abi_encode_with_selector_hash.sol +++ b/test/libsolidity/smtCheckerTests/abi/abi_encode_with_selector_hash.sol @@ -3,7 +3,8 @@ contract C { require(a == b); bytes memory b1 = abi.encodeWithSelector(sel, a, a, a, a); bytes memory b2 = abi.encodeWithSelector(sel, b, a, b, a); - assert(keccak256(b1) == keccak256(b2)); + // Disabled because of OSX nondeterminism + //assert(keccak256(b1) == keccak256(b2)); bytes memory b3 = abi.encodeWithSelector(0xcafecafe, a, a, a, a); assert(keccak256(b1) == keccak256(b3)); // should fail @@ -13,9 +14,10 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 1218: (333-371): CHC: Error trying to invoke SMT solver. -// Warning 1218: (390-428): CHC: Error trying to invoke SMT solver. -// Warning 6328: (333-371): CHC: Assertion violation might happen here. -// Warning 6328: (390-428): CHC: Assertion violation might happen here. -// Warning 4661: (333-371): BMC: Assertion violation happens here. -// Warning 4661: (390-428): BMC: Assertion violation happens here. +// Warning 2072: (161-176): Unused local variable. +// Warning 1218: (379-417): CHC: Error trying to invoke SMT solver. +// Warning 1218: (436-474): CHC: Error trying to invoke SMT solver. +// Warning 6328: (379-417): CHC: Assertion violation might happen here. +// Warning 6328: (436-474): CHC: Assertion violation might happen here. +// Warning 4661: (379-417): BMC: Assertion violation happens here. +// Warning 4661: (436-474): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol index fd0229d78..31eca9e92 100644 --- a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol +++ b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol @@ -18,5 +18,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (265-310): CHC: Assertion violation happens here.\nCounterexample:\nb = [0x01]\none = 0x01\n\nTransaction trace:\nC.constructor()\nState: b = []\nC.g() +// Warning 6328: (265-310): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes_2d.sol b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes_2d.sol index 7151d9304..fcceeebfd 100644 --- a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes_2d.sol +++ b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes_2d.sol @@ -22,5 +22,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- // Warning 6328: (435-508): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/functions/getters/array_1.sol b/test/libsolidity/smtCheckerTests/functions/getters/array_1.sol index 421c4fd4f..d44d15f28 100644 --- a/test/libsolidity/smtCheckerTests/functions/getters/array_1.sol +++ b/test/libsolidity/smtCheckerTests/functions/getters/array_1.sol @@ -14,6 +14,7 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (187-201): CHC: Assertion violation happens here.\nCounterexample:\na = [0, 0, 0, 0]\ny = 0\n\nTransaction trace:\nC.constructor()\nState: a = [0, 0, 0, 0]\nC.f() +// Warning 6328: (187-201): CHC: Assertion violation happens here. // Info 1180: Contract invariant(s) for :C:\n!(a.length <= 2)\n diff --git a/test/libsolidity/smtCheckerTests/overflow/unsigned_mul_overflow.sol b/test/libsolidity/smtCheckerTests/overflow/unsigned_mul_overflow.sol index 0a6d85a7e..e809e041c 100644 --- a/test/libsolidity/smtCheckerTests/overflow/unsigned_mul_overflow.sol +++ b/test/libsolidity/smtCheckerTests/overflow/unsigned_mul_overflow.sol @@ -5,5 +5,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 4984: (80-85): CHC: Overflow (resulting value larger than 2**256 - 1) happens here.\nCounterexample:\n\nx = 57896044618658097711785492504343953926634992332820282019728792003956564819968\ny = 2\n = 0\n\nTransaction trace:\nC.constructor()\nC.f(57896044618658097711785492504343953926634992332820282019728792003956564819968, 2) +// Warning 4984: (80-85): CHC: Overflow (resulting value larger than 2**256 - 1) happens here. diff --git a/test/libsolidity/smtCheckerTests/try_catch/try_nested_2.sol b/test/libsolidity/smtCheckerTests/try_catch/try_nested_2.sol index 98a19731b..5c15fb54f 100644 --- a/test/libsolidity/smtCheckerTests/try_catch/try_nested_2.sol +++ b/test/libsolidity/smtCheckerTests/try_catch/try_nested_2.sol @@ -19,5 +19,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (342-362): CHC: Assertion violation happens here.\nCounterexample:\n\nchoice = 3\n\nTransaction trace:\nC.constructor()\nC.f() +// Warning 6328: (342-362): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol index 8779cd095..f9ae95e7c 100644 --- a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol +++ b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol @@ -25,5 +25,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (307-327): CHC: Assertion violation happens here.\nCounterexample:\nt = {x: 10, s: {innerM, sum: 21239}}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: t = {x: 10, s: {innerM, sum: 21239}}\nC.g() +// Warning 6328: (307-327): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol b/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol index d3589fed5..9d8df15eb 100644 --- a/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol +++ b/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol @@ -9,5 +9,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 4984: (96-101): CHC: Overflow (resulting value larger than 65535) happens here.\nCounterexample:\n\na = 65535\nb = 1\n = 0\n\nTransaction trace:\nC.constructor()\nC.add(65535, 1) +// Warning 4984: (96-101): CHC: Overflow (resulting value larger than 65535) happens here. From 201c6c68195b9dd72fa08c6f055aa7ff67fbabfe Mon Sep 17 00:00:00 2001 From: Leo Alt Date: Thu, 5 May 2022 11:38:16 +0200 Subject: [PATCH 12/21] fix smt flaky test --- .../smtCheckerTests/operators/slice_default_end.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/libsolidity/smtCheckerTests/operators/slice_default_end.sol b/test/libsolidity/smtCheckerTests/operators/slice_default_end.sol index 9c0db9007..c6f421a0c 100644 --- a/test/libsolidity/smtCheckerTests/operators/slice_default_end.sol +++ b/test/libsolidity/smtCheckerTests/operators/slice_default_end.sol @@ -3,8 +3,9 @@ contract C { require(b.length == 30); require(b[10] == 0xff); require(b[b.length - 1] == 0xaa); - assert(bytes(b[10:]).length == 20); - assert(bytes(b[10:])[0] == 0xff); + assert(bytes(b[10:]).length == 20); // should hold + // Disabled because of Spacer's nondeterminism. + //assert(bytes(b[10:])[0] == 0xff); // should hold //assert(bytes(b[10:])[5] == 0xff); // Removed because of Spacer's nondeterminism //assert(bytes(b[10:])[19] == 0xaa); // Removed because of Spacer nondeterminism } @@ -12,5 +13,3 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 6328: (188-220): CHC: Assertion violation might happen here. -// Warning 4661: (188-220): BMC: Assertion violation happens here. From 1ccdb92cdb45ae4324df352be3dc3b536851747e Mon Sep 17 00:00:00 2001 From: Marenz Date: Mon, 2 May 2022 14:23:52 +0200 Subject: [PATCH 13/21] Update version & distributions for static z3 script --- scripts/deps-ppa/static_z3.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/deps-ppa/static_z3.sh b/scripts/deps-ppa/static_z3.sh index 353a8a8d9..79393221c 100755 --- a/scripts/deps-ppa/static_z3.sh +++ b/scripts/deps-ppa/static_z3.sh @@ -25,9 +25,9 @@ set -ev keyid=70D110489D66E2F6 email=builds@ethereum.org packagename=z3-static -version=4.8.14 +version=4.8.16 -DISTRIBUTIONS="focal hirsute impish jammy" +DISTRIBUTIONS="focal impish jammy" for distribution in $DISTRIBUTIONS do From c7d57031b56f7c0513f2e790ad99b05abfe784ea Mon Sep 17 00:00:00 2001 From: Florian Sey Date: Fri, 6 May 2022 01:01:33 +0200 Subject: [PATCH 14/21] Improve wording on voting example From a beginner perspective, it is the first time in the documentation that the term wallets is used. Other terms such as accounts or addresses are explained in the Introduction to smart contracts. --- docs/examples/voting.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/voting.rst b/docs/examples/voting.rst index a51c01b74..6e36f4e51 100644 --- a/docs/examples/voting.rst +++ b/docs/examples/voting.rst @@ -133,7 +133,7 @@ of votes. // modifies `voters[msg.sender].voted` Voter storage delegate_ = voters[to]; - // Voters cannot delegate to wallets that cannot vote. + // Voters cannot delegate to accounts that cannot vote. require(delegate_.weight >= 1); sender.voted = true; sender.delegate = to; From 49d27eaa5d28a24aa2eb7baa3afc48ac3c3f9dda Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Wed, 16 Mar 2022 15:44:52 +0100 Subject: [PATCH 15/21] [Circle CI] Adds LSP tests to Windows CI. --- .circleci/config.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2d824c249..e997fb322 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1280,6 +1280,12 @@ jobs: - run: name: "Run soltest" command: .circleci/soltest.ps1 + - run: + name: Install LSP test dependencies + command: python -m pip install --user deepdiff colorama + - run: + name: Executing solc LSP test suite + command: python ./test/lsp.py .\build\solc\Release\solc.exe - store_test_results: *store_test_results - store_artifacts: *artifacts_test_results - gitter_notify_failure_unless_pr From f308f1a1f82acea226d70af16dd1ca14a75a9a6d Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 25 Apr 2022 15:35:41 +0200 Subject: [PATCH 16/21] Always allow full filesystem access to LSP. --- Changelog.md | 1 + libsolidity/CMakeLists.txt | 1 + libsolidity/lsp/FileRepository.cpp | 128 ++++++++++++++---- libsolidity/lsp/FileRepository.h | 40 ++++-- libsolidity/lsp/HandlerBase.cpp | 4 +- libsolidity/lsp/LanguageServer.cpp | 19 ++- libsolidity/lsp/LanguageServer.h | 2 +- libsolidity/lsp/Transport.cpp | 200 +++++++++++++++++++---------- libsolidity/lsp/Transport.h | 69 +++++++--- libsolidity/lsp/Utils.cpp | 13 +- libsolidity/lsp/Utils.h | 7 + solc/CommandLineInterface.cpp | 2 +- 12 files changed, 350 insertions(+), 136 deletions(-) diff --git a/Changelog.md b/Changelog.md index 01c288a2e..27ae4e30a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -8,6 +8,7 @@ Compiler Features: * Assembly-Json: Export: Include source list in `sourceList` field. * Commandline Interface: option ``--pretty-json`` works also with the following options: ``--abi``, ``--asm-json``, ``--ast-compact-json``, ``--devdoc``, ``--storage-layout``, ``--userdoc``. * SMTChecker: Support ``abi.encodeCall`` taking into account the called selector. + * Language Server: Allow full filesystem access to language server. Bugfixes: diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 3d2845463..26c14976e 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -176,3 +176,4 @@ set(sources add_library(solidity ${sources}) target_link_libraries(solidity PUBLIC yul evmasm langutil smtutil solutil Boost::boost fmt::fmt-header-only) + diff --git a/libsolidity/lsp/FileRepository.cpp b/libsolidity/lsp/FileRepository.cpp index 9c7f72e0c..e82343973 100644 --- a/libsolidity/lsp/FileRepository.cpp +++ b/libsolidity/lsp/FileRepository.cpp @@ -17,48 +17,130 @@ // SPDX-License-Identifier: GPL-3.0 #include +#include + +#include +#include + +#include +#include + +#include using namespace std; using namespace solidity; using namespace solidity::lsp; +using namespace solidity::frontend; -namespace -{ +using solidity::util::readFileAsString; +using solidity::util::joinHumanReadable; -string stripFilePrefix(string const& _path) +FileRepository::FileRepository(boost::filesystem::path _basePath): m_basePath(std::move(_basePath)) { - if (_path.find("file://") == 0) - return _path.substr(7); - else - return _path; } -} - -string FileRepository::sourceUnitNameToClientPath(string const& _sourceUnitName) const +string FileRepository::sourceUnitNameToUri(string const& _sourceUnitName) const { - if (m_sourceUnitNamesToClientPaths.count(_sourceUnitName)) - return m_sourceUnitNamesToClientPaths.at(_sourceUnitName); + regex const windowsDriveLetterPath("^[a-zA-Z]:/"); + + if (m_sourceUnitNamesToUri.count(_sourceUnitName)) + return m_sourceUnitNamesToUri.at(_sourceUnitName); else if (_sourceUnitName.find("file://") == 0) return _sourceUnitName; + else if (regex_search(_sourceUnitName, windowsDriveLetterPath)) + return "file:///" + _sourceUnitName; + else if (_sourceUnitName.find("/") == 0) + return "file://" + _sourceUnitName; else - return "file://" + (m_fileReader.basePath() / _sourceUnitName).generic_string(); + return "file://" + m_basePath.generic_string() + "/" + _sourceUnitName; } -string FileRepository::clientPathToSourceUnitName(string const& _path) const +string FileRepository::uriToSourceUnitName(string const& _path) const { - return m_fileReader.cliPathToSourceUnitName(stripFilePrefix(_path)); + return stripFileUriSchemePrefix(_path); } -map const& FileRepository::sourceUnits() const -{ - return m_fileReader.sourceUnits(); -} - -void FileRepository::setSourceByClientPath(string const& _uri, string _text) +void FileRepository::setSourceByUri(string const& _uri, string _source) { // This is needed for uris outside the base path. It can lead to collisions, // but we need to mostly rewrite this in a future version anyway. - m_sourceUnitNamesToClientPaths.emplace(clientPathToSourceUnitName(_uri), _uri); - m_fileReader.addOrUpdateFile(stripFilePrefix(_uri), move(_text)); + auto sourceUnitName = uriToSourceUnitName(_uri); + m_sourceUnitNamesToUri.emplace(sourceUnitName, _uri); + m_sourceCodes[sourceUnitName] = std::move(_source); } + +frontend::ReadCallback::Result FileRepository::readFile(string const& _kind, string const& _sourceUnitName) +{ + solAssert( + _kind == ReadCallback::kindString(ReadCallback::Kind::ReadFile), + "ReadFile callback used as callback kind " + _kind + ); + + try + { + // File was read already. Use local store. + if (m_sourceCodes.count(_sourceUnitName)) + return ReadCallback::Result{true, m_sourceCodes.at(_sourceUnitName)}; + + string const strippedSourceUnitName = stripFileUriSchemePrefix(_sourceUnitName); + + if ( + boost::filesystem::path(strippedSourceUnitName).has_root_path() && + boost::filesystem::exists(strippedSourceUnitName) + ) + { + auto contents = readFileAsString(strippedSourceUnitName); + solAssert(m_sourceCodes.count(_sourceUnitName) == 0, ""); + m_sourceCodes[_sourceUnitName] = contents; + return ReadCallback::Result{true, move(contents)}; + } + + vector candidates; + vector> prefixes = {m_basePath}; + prefixes += (m_includePaths | ranges::to>>); + + auto const pathToQuotedString = [](boost::filesystem::path const& _path) { return "\"" + _path.string() + "\""; }; + + for (auto const& prefix: prefixes) + { + boost::filesystem::path canonicalPath = boost::filesystem::path(prefix) / boost::filesystem::path(strippedSourceUnitName); + + if (boost::filesystem::exists(canonicalPath)) + candidates.push_back(move(canonicalPath)); + } + + if (candidates.empty()) + return ReadCallback::Result{ + false, + "File not found. Searched the following locations: " + + joinHumanReadable(prefixes | ranges::views::transform(pathToQuotedString), ", ") + + "." + }; + + if (candidates.size() >= 2) + return ReadCallback::Result{ + false, + "Ambiguous import. " + "Multiple matching files found inside base path and/or include paths: " + + joinHumanReadable(candidates | ranges::views::transform(pathToQuotedString), ", ") + + "." + }; + + if (!boost::filesystem::is_regular_file(candidates[0])) + return ReadCallback::Result{false, "Not a valid file."}; + + auto contents = readFileAsString(candidates[0]); + solAssert(m_sourceCodes.count(_sourceUnitName) == 0, ""); + m_sourceCodes[_sourceUnitName] = contents; + return ReadCallback::Result{true, move(contents)}; + } + catch (std::exception const& _exception) + { + return ReadCallback::Result{false, "Exception in read callback: " + boost::diagnostic_information(_exception)}; + } + catch (...) + { + return ReadCallback::Result{false, "Unknown exception in read callback: " + boost::current_exception_diagnostic_information()}; + } +} + diff --git a/libsolidity/lsp/FileRepository.h b/libsolidity/lsp/FileRepository.h index b6aa5ee08..152c1d4be 100644 --- a/libsolidity/lsp/FileRepository.h +++ b/libsolidity/lsp/FileRepository.h @@ -28,26 +28,42 @@ namespace solidity::lsp class FileRepository { public: - explicit FileRepository(boost::filesystem::path const& _basePath): - m_fileReader(_basePath) {} + explicit FileRepository(boost::filesystem::path _basePath); - boost::filesystem::path const& basePath() const { return m_fileReader.basePath(); } + boost::filesystem::path const& basePath() const { return m_basePath; } /// Translates a compiler-internal source unit name to an LSP client path. - std::string sourceUnitNameToClientPath(std::string const& _sourceUnitName) const; - /// Translates an LSP client path into a compiler-internal source unit name. - std::string clientPathToSourceUnitName(std::string const& _uri) const; + std::string sourceUnitNameToUri(std::string const& _sourceUnitName) const; + + /// Translates an LSP file URI into a compiler-internal source unit name. + std::string uriToSourceUnitName(std::string const& _uri) const; /// @returns all sources by their compiler-internal source unit name. - std::map const& sourceUnits() const; - /// Changes the source identified by the LSP client path _uri to _text. - void setSourceByClientPath(std::string const& _uri, std::string _text); + StringMap const& sourceUnits() const noexcept { return m_sourceCodes; } - frontend::ReadCallback::Callback reader() { return m_fileReader.reader(); } + /// Changes the source identified by the LSP client path _uri to _text. + void setSourceByUri(std::string const& _uri, std::string _text); + + void addOrUpdateFile(boost::filesystem::path const& _path, frontend::SourceCode _source); + void setSourceUnits(StringMap _sources); + frontend::ReadCallback::Result readFile(std::string const& _kind, std::string const& _sourceUnitName); + frontend::ReadCallback::Callback reader() + { + return [this](std::string const& _kind, std::string const& _path) { return readFile(_kind, _path); }; + } private: - std::map m_sourceUnitNamesToClientPaths; - frontend::FileReader m_fileReader; + /// Base path without URI scheme. + boost::filesystem::path m_basePath; + + /// Additional directories used for resolving relative paths in imports. + std::vector m_includePaths; + + /// Mapping of source unit names to their URIs as understood by the client. + StringMap m_sourceUnitNamesToUri; + + /// Mapping of source unit names to their file content. + StringMap m_sourceCodes; }; } diff --git a/libsolidity/lsp/HandlerBase.cpp b/libsolidity/lsp/HandlerBase.cpp index 0da19aad3..f40f189ab 100644 --- a/libsolidity/lsp/HandlerBase.cpp +++ b/libsolidity/lsp/HandlerBase.cpp @@ -47,7 +47,7 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const { solAssert(_location.sourceName); Json::Value item = Json::objectValue; - item["uri"] = fileRepository().sourceUnitNameToClientPath(*_location.sourceName); + item["uri"] = fileRepository().sourceUnitNameToUri(*_location.sourceName); item["range"] = toRange(_location); return item; } @@ -55,7 +55,7 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const pair HandlerBase::extractSourceUnitNameAndLineColumn(Json::Value const& _args) const { string const uri = _args["textDocument"]["uri"].asString(); - string const sourceUnitName = fileRepository().clientPathToSourceUnitName(uri); + string const sourceUnitName = fileRepository().uriToSourceUnitName(uri); if (!fileRepository().sourceUnits().count(sourceUnitName)) BOOST_THROW_EXCEPTION( RequestError(ErrorCode::RequestFailed) << diff --git a/libsolidity/lsp/LanguageServer.cpp b/libsolidity/lsp/LanguageServer.cpp index 8a42ca104..44b0cf041 100644 --- a/libsolidity/lsp/LanguageServer.cpp +++ b/libsolidity/lsp/LanguageServer.cpp @@ -37,8 +37,6 @@ #include #include -#include - #include #include @@ -114,9 +112,9 @@ void LanguageServer::compile() swap(oldRepository, m_fileRepository); for (string const& fileName: m_openFiles) - m_fileRepository.setSourceByClientPath( + m_fileRepository.setSourceByUri( fileName, - oldRepository.sourceUnits().at(oldRepository.clientPathToSourceUnitName(fileName)) + oldRepository.sourceUnits().at(oldRepository.uriToSourceUnitName(fileName)) ); // TODO: optimize! do not recompile if nothing has changed (file(s) not flagged dirty). @@ -178,7 +176,7 @@ void LanguageServer::compileAndUpdateDiagnostics() for (auto&& [sourceUnitName, diagnostics]: diagnosticsBySourceUnit) { Json::Value params; - params["uri"] = m_fileRepository.sourceUnitNameToClientPath(sourceUnitName); + params["uri"] = m_fileRepository.sourceUnitNameToUri(sourceUnitName); if (!diagnostics.empty()) m_nonemptyDiagnostics.insert(sourceUnitName); params["diagnostics"] = move(diagnostics); @@ -252,13 +250,12 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args) ErrorCode::InvalidParams, "rootUri only supports file URI scheme." ); - - rootPath = rootPath.substr(7); + rootPath = stripFileUriSchemePrefix(rootPath); } else if (Json::Value rootPath = _args["rootPath"]) rootPath = rootPath.asString(); - m_fileRepository = FileRepository(boost::filesystem::path(rootPath)); + m_fileRepository = FileRepository(rootPath); if (_args["initializationOptions"].isObject()) changeConfiguration(_args["initializationOptions"]); @@ -309,7 +306,7 @@ void LanguageServer::handleTextDocumentDidOpen(Json::Value const& _args) string text = _args["textDocument"]["text"].asString(); string uri = _args["textDocument"]["uri"].asString(); m_openFiles.insert(uri); - m_fileRepository.setSourceByClientPath(uri, move(text)); + m_fileRepository.setSourceByUri(uri, move(text)); compileAndUpdateDiagnostics(); } @@ -327,7 +324,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args) "Invalid content reference." ); - string const sourceUnitName = m_fileRepository.clientPathToSourceUnitName(uri); + string const sourceUnitName = m_fileRepository.uriToSourceUnitName(uri); lspAssert( m_fileRepository.sourceUnits().count(sourceUnitName), ErrorCode::RequestFailed, @@ -348,7 +345,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args) buffer.replace(static_cast(change->start), static_cast(change->end - change->start), move(text)); text = move(buffer); } - m_fileRepository.setSourceByClientPath(uri, move(text)); + m_fileRepository.setSourceByUri(uri, move(text)); } compileAndUpdateDiagnostics(); diff --git a/libsolidity/lsp/LanguageServer.h b/libsolidity/lsp/LanguageServer.h index e827f6259..2a7951fb0 100644 --- a/libsolidity/lsp/LanguageServer.h +++ b/libsolidity/lsp/LanguageServer.h @@ -92,7 +92,7 @@ private: Transport& m_client; std::map m_handlers; - /// Set of files known to be open by the client. + /// Set of files (names in URI form) known to be open by the client. std::set m_openFiles; /// Set of source unit names for which we sent diagnostics to the client in the last iteration. std::set m_nonemptyDiagnostics; diff --git a/libsolidity/lsp/Transport.cpp b/libsolidity/lsp/Transport.cpp index b82b34ec4..aa85fd6b1 100644 --- a/libsolidity/lsp/Transport.cpp +++ b/libsolidity/lsp/Transport.cpp @@ -22,31 +22,25 @@ #include #include +#include + #include #include #include +#include + + +#if defined(_WIN32) +#include +#include +#endif using namespace std; using namespace solidity::lsp; -IOStreamTransport::IOStreamTransport(istream& _in, ostream& _out): - m_input{_in}, - m_output{_out} -{ -} - -IOStreamTransport::IOStreamTransport(): - IOStreamTransport(cin, cout) -{ -} - -bool IOStreamTransport::closed() const noexcept -{ - return m_input.eof(); -} - -optional IOStreamTransport::receive() +// {{{ Transport +optional Transport::receive() { auto const headers = parseHeaders(); if (!headers) @@ -61,7 +55,7 @@ optional IOStreamTransport::receive() return nullopt; } - string const data = util::readBytes(m_input, stoul(headers->at("content-length"))); + string const data = readBytes(stoul(headers->at("content-length"))); Json::Value jsonMessage; string jsonParsingErrors; @@ -75,29 +69,6 @@ optional IOStreamTransport::receive() return {move(jsonMessage)}; } -void IOStreamTransport::notify(string _method, Json::Value _message) -{ - Json::Value json; - json["method"] = move(_method); - json["params"] = move(_message); - send(move(json)); -} - -void IOStreamTransport::reply(MessageID _id, Json::Value _message) -{ - Json::Value json; - json["result"] = move(_message); - send(move(json), _id); -} - -void IOStreamTransport::error(MessageID _id, ErrorCode _code, string _message) -{ - Json::Value json; - json["error"]["code"] = static_cast(_code); - json["error"]["message"] = move(_message); - send(move(json), _id); -} - void Transport::trace(std::string _message, Json::Value _extra) { if (m_logTrace != TraceValue::Off) @@ -110,30 +81,13 @@ void Transport::trace(std::string _message, Json::Value _extra) } } -void IOStreamTransport::send(Json::Value _json, MessageID _id) -{ - solAssert(_json.isObject()); - _json["jsonrpc"] = "2.0"; - if (_id != Json::nullValue) - _json["id"] = _id; - - string const jsonString = solidity::util::jsonCompactPrint(_json); - - m_output << "Content-Length: " << jsonString.size() << "\r\n"; - m_output << "\r\n"; - m_output << jsonString; - - m_output.flush(); -} - -optional> IOStreamTransport::parseHeaders() +optional> Transport::parseHeaders() { map headers; while (true) { - string line; - getline(m_input, line); + auto line = getline(); if (boost::trim_copy(line).empty()) break; @@ -141,13 +95,127 @@ optional> IOStreamTransport::parseHeaders() if (delimiterPos == string::npos) return nullopt; - string name = boost::to_lower_copy(line.substr(0, delimiterPos)); - string value = line.substr(delimiterPos + 1); - if (!headers.emplace( - boost::trim_copy(name), - boost::trim_copy(value) - ).second) + auto const name = boost::to_lower_copy(line.substr(0, delimiterPos)); + auto const value = line.substr(delimiterPos + 1); + if (!headers.emplace(boost::trim_copy(name), boost::trim_copy(value)).second) return nullopt; } return {move(headers)}; } + +void Transport::notify(string _method, Json::Value _message) +{ + Json::Value json; + json["method"] = move(_method); + json["params"] = move(_message); + send(move(json)); +} + +void Transport::reply(MessageID _id, Json::Value _message) +{ + Json::Value json; + json["result"] = move(_message); + send(move(json), _id); +} + +void Transport::error(MessageID _id, ErrorCode _code, string _message) +{ + Json::Value json; + json["error"]["code"] = static_cast(_code); + json["error"]["message"] = move(_message); + send(move(json), _id); +} + +void Transport::send(Json::Value _json, MessageID _id) +{ + solAssert(_json.isObject()); + _json["jsonrpc"] = "2.0"; + if (_id != Json::nullValue) + _json["id"] = _id; + + // Trailing CRLF only for easier readability. + string const jsonString = solidity::util::jsonCompactPrint(_json); + + writeBytes(fmt::format("Content-Length: {}\r\n\r\n", jsonString.size())); + writeBytes(jsonString); + flushOutput(); +} +// }}} + +// {{{ IOStreamTransport +IOStreamTransport::IOStreamTransport(istream& _in, ostream& _out): + m_input{_in}, + m_output{_out} +{ +} + +bool IOStreamTransport::closed() const noexcept +{ + return m_input.eof(); +} + +std::string IOStreamTransport::readBytes(size_t _length) +{ + return util::readBytes(m_input, _length); +} + +std::string IOStreamTransport::getline() +{ + string line; + std::getline(m_input, line); + return line; +} + +void IOStreamTransport::writeBytes(std::string_view _data) +{ + m_output.write(_data.data(), static_cast(_data.size())); +} + +void IOStreamTransport::flushOutput() +{ + m_output.flush(); +} +// }}} + +// {{{ StdioTransport +StdioTransport::StdioTransport() +{ + #if defined(_WIN32) + // Attempt to change the modes of stdout from text to binary. + setmode(fileno(stdout), O_BINARY); + #endif +} + +bool StdioTransport::closed() const noexcept +{ + return feof(stdin); +} + +std::string StdioTransport::readBytes(size_t _byteCount) +{ + std::string buffer; + buffer.resize(_byteCount); + auto const n = fread(buffer.data(), 1, _byteCount, stdin); + if (n < _byteCount) + buffer.resize(n); + return buffer; +} + +std::string StdioTransport::getline() +{ + std::string line; + std::getline(std::cin, line); + return line; +} + +void StdioTransport::writeBytes(std::string_view _data) +{ + auto const bytesWritten = fwrite(_data.data(), 1, _data.size(), stdout); + solAssert(bytesWritten == _data.size()); +} + +void StdioTransport::flushOutput() +{ + fflush(stdout); +} +// }}} diff --git a/libsolidity/lsp/Transport.h b/libsolidity/lsp/Transport.h index d84edf49d..8aa89b3d2 100644 --- a/libsolidity/lsp/Transport.h +++ b/libsolidity/lsp/Transport.h @@ -91,20 +91,44 @@ class Transport public: virtual ~Transport() = default; + std::optional receive(); + void notify(std::string _method, Json::Value _params); + void reply(MessageID _id, Json::Value _result); + void error(MessageID _id, ErrorCode _code, std::string _message); + virtual bool closed() const noexcept = 0; - virtual std::optional receive() = 0; - virtual void notify(std::string _method, Json::Value _params) = 0; - virtual void reply(MessageID _id, Json::Value _result) = 0; - virtual void error(MessageID _id, ErrorCode _code, std::string _message) = 0; void trace(std::string _message, Json::Value _extra = Json::nullValue); TraceValue traceValue() const noexcept { return m_logTrace; } void setTrace(TraceValue _value) noexcept { m_logTrace = _value; } - private: TraceValue m_logTrace = TraceValue::Off; + +protected: + /// Reads from the transport and parses the headers until the beginning + /// of the contents. + std::optional> parseHeaders(); + + /// Consumes exactly @p _byteCount bytes, as needed for consuming + /// the message body from the transport line. + virtual std::string readBytes(size_t _byteCount) = 0; + + // Mimmicks std::getline() on this Transport API. + virtual std::string getline() = 0; + + /// Writes the given payload @p _data to transport. + /// This call may or may not buffer. + virtual void writeBytes(std::string_view _data) = 0; + + /// Ensures transport output is flushed. + virtual void flushOutput() = 0; + + /// Sends an arbitrary raw message to the client. + /// + /// Used by the notify/reply/error function family. + virtual void send(Json::Value _message, MessageID _id = Json::nullValue); }; /** @@ -119,27 +143,34 @@ public: /// @param _out for example std::cout (stdout) IOStreamTransport(std::istream& _in, std::ostream& _out); - // Constructs a JSON transport using standard I/O streams. - IOStreamTransport(); - bool closed() const noexcept override; - std::optional receive() override; - void notify(std::string _method, Json::Value _params) override; - void reply(MessageID _id, Json::Value _result) override; - void error(MessageID _id, ErrorCode _code, std::string _message) override; protected: - /// Sends an arbitrary raw message to the client. - /// - /// Used by the notify/reply/error function family. - virtual void send(Json::Value _message, MessageID _id = Json::nullValue); - - /// Parses header section from the client including message-delimiting empty line. - std::optional> parseHeaders(); + std::string readBytes(size_t _byteCount) override; + std::string getline() override; + void writeBytes(std::string_view _data) override; + void flushOutput() override; private: std::istream& m_input; std::ostream& m_output; }; +/** + * Standard I/O transport Layer utilizing stdin/stdout for communication. + */ +class StdioTransport: public Transport +{ +public: + StdioTransport(); + + bool closed() const noexcept override; + +protected: + std::string readBytes(size_t _byteCount) override; + std::string getline() override; + void writeBytes(std::string_view _data) override; + void flushOutput() override; +}; + } diff --git a/libsolidity/lsp/Utils.cpp b/libsolidity/lsp/Utils.cpp index 624d1f150..ef19b4c2e 100644 --- a/libsolidity/lsp/Utils.cpp +++ b/libsolidity/lsp/Utils.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include namespace solidity::lsp @@ -115,4 +115,15 @@ optional parseRange(FileRepository const& _fileRepository, strin return start; } +string stripFileUriSchemePrefix(string const& _path) +{ + regex const windowsDriveLetterPath("^file:///[a-zA-Z]:/"); + if (regex_search(_path, windowsDriveLetterPath)) + return _path.substr(8); + if (_path.find("file://") == 0) + return _path.substr(7); + else + return _path; +} + } diff --git a/libsolidity/lsp/Utils.h b/libsolidity/lsp/Utils.h index 3594efba2..c6d40213e 100644 --- a/libsolidity/lsp/Utils.h +++ b/libsolidity/lsp/Utils.h @@ -64,6 +64,13 @@ std::optional parseRange( Json::Value const& _range ); +/// Strips the file:// URI prefix off the given path, if present, +/// also taking special care of Windows-drive-letter paths. +/// +/// So file:///path/to/some/file.txt returns /path/to/some/file.txt, as well as, +/// file:///C:/file.txt will return C:/file.txt (forward-slash is okay on Windows). +std::string stripFileUriSchemePrefix(std::string const& _path); + /// Extracts the resolved declaration of the given expression AST node. /// /// This may for example be the type declaration of an identifier, diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 40a428c95..7e627208f 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -912,7 +912,7 @@ void CommandLineInterface::handleAst() void CommandLineInterface::serveLSP() { - lsp::IOStreamTransport transport; + lsp::StdioTransport transport; if (!lsp::LanguageServer{transport}.run()) solThrow(CommandLineExecutionError, "LSP terminated abnormally."); } From c2f245b40a20bc053c744ae6011df6bf7150d0d8 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 25 Apr 2022 15:35:24 +0200 Subject: [PATCH 17/21] Fixes to lsp.py with respect to Windows drive-letter paths in URI. --- test/lsp.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/lsp.py b/test/lsp.py index 79be59f8e..33059c43d 100755 --- a/test/lsp.py +++ b/test/lsp.py @@ -12,6 +12,7 @@ import tty import functools from collections import namedtuple from copy import deepcopy +from pathlib import PurePath from typing import Any, List, Optional, Tuple, Union from itertools import islice @@ -694,7 +695,7 @@ class SolidityLSPTestSuite: # {{{ args = create_cli_parser().parse_args() self.solc_path = args.solc_path self.project_root_dir = os.path.realpath(args.project_root_dir) + "/test/libsolidity/lsp" - self.project_root_uri = "file://" + self.project_root_dir + self.project_root_uri = PurePath(self.project_root_dir).as_uri() self.print_assertions = args.print_assertions self.trace_io = args.trace_io self.test_pattern = args.test_pattern @@ -777,7 +778,7 @@ class SolidityLSPTestSuite: # {{{ return f"{self.project_root_dir}/{test_case_name}.sol" def get_test_file_uri(self, test_case_name): - return "file://" + self.get_test_file_path(test_case_name) + return PurePath(self.get_test_file_path(test_case_name)).as_uri() def get_test_file_contents(self, test_case_name): """ @@ -1438,7 +1439,7 @@ class SolidityLSPTestSuite: # {{{ """ self.setup_lsp(solc) - FILE_A_URI = f'file://{self.project_root_dir}/a.sol' + FILE_A_URI = f'{self.project_root_uri}/a.sol' solc.send_message('textDocument/didOpen', { 'textDocument': { 'uri': FILE_A_URI, @@ -1466,7 +1467,7 @@ class SolidityLSPTestSuite: # {{{ ) reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 1, '') - self.expect_equal(reports[0]['uri'], f'file://{self.project_root_dir}/lib.sol', "") + self.expect_equal(reports[0]['uri'], f'{self.project_root_uri}/lib.sol', "") self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") def test_textDocument_didChange_at_eol(self, solc: JsonRpcProcess) -> None: From e8d07772d907c3f7ec57b8e3f5a9be03273d72a1 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 25 Apr 2022 17:21:45 +0200 Subject: [PATCH 18/21] lsp.py: Port to support running on Windows & adapt to changes due to prior merged PR. - lsp.py: Fixes invalid-syntax by Python interpreter on Windows CI (older Python version). - lsp.py: Savely strip CRLF from right side of the string, ignoring accidental multiple occurrences of \r (such as \r\r\n). - lsp.py: Fixes reading single character from stdin (wrt. Windows platform). - lsp.py: Adds header line reading to I/O tracing (useful for debugging). - lsp.py: When running the tests on Windows, don't care test file content's newlines but simply expect LFs (instead of CRLF for example). - Apply pylint notes. - Fixing use of @functools.lru_cache for older python versions (CircleCI Windows) --- .circleci/config.yml | 3 ++ test/lsp.py | 70 +++++++++++++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e997fb322..06213ebcb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1283,6 +1283,9 @@ jobs: - run: name: Install LSP test dependencies command: python -m pip install --user deepdiff colorama + - run: + name: Inspect lsp.py + command: Get-Content ./test/lsp.py - run: name: Executing solc LSP test suite command: python ./test/lsp.py .\build\solc\Release\solc.exe diff --git a/test/lsp.py b/test/lsp.py index 33059c43d..3217fb79b 100755 --- a/test/lsp.py +++ b/test/lsp.py @@ -1,26 +1,57 @@ #!/usr/bin/env python3 # pragma pylint: disable=too-many-lines +# test line 1 import argparse import fnmatch +import functools import json import os +import re import subprocess import sys import traceback -import re -import tty -import functools from collections import namedtuple from copy import deepcopy +from enum import Enum, auto +from itertools import islice from pathlib import PurePath from typing import Any, List, Optional, Tuple, Union -from itertools import islice -from enum import Enum, auto - -import colorama # Enables the use of SGR & CUP terminal VT sequences on Windows. +import colorama # Enables the use of SGR & CUP terminal VT sequences on Windows. from deepdiff import DeepDiff +if os.name == 'nt': + # pragma pylint: disable=import-error + import msvcrt +else: + import tty + # Turn off user input buffering so we get the input immediately, + # not only after a line break + tty.setcbreak(sys.stdin.fileno()) + + +def escape_string(text: str) -> str: + """ + Trivially escapes given input string's \r \n and \\. + """ + return text.translate(str.maketrans({ + "\r": r"\r", + "\n": r"\n", + "\\": r"\\" + })) + + +def getCharFromStdin(): + """ + Gets a single character from stdin without line-buffering. + """ + if os.name == 'nt': + # pragma pylint: disable=import-error + return msvcrt.getch().decode("utf-8") + else: + return sys.stdin.buffer.read(1) + + """ Named tuple that holds various regexes used to parse the test specification. """ @@ -101,7 +132,6 @@ class BadHeader(Exception): def __init__(self, msg: str): super().__init__("Bad header: " + msg) - class JsonRpcProcess: exe_path: str exe_args: List[str] @@ -144,10 +174,12 @@ class JsonRpcProcess: # server quit return None line = line.decode("utf-8") + if self.trace_io: + print(f"Received header-line: {escape_string(line)}") if not line.endswith("\r\n"): raise BadHeader("missing newline") - # remove the "\r\n" - line = line[:-2] + # Safely remove the "\r\n". + line = line.rstrip("\r\n") if line == '': break # done with the headers if line.startswith(CONTENT_LENGTH_HEADER): @@ -589,7 +621,7 @@ class FileTestRunner: while True: print("(u)pdate/(r)etry/(i)gnore?") - user_response = sys.stdin.read(1) + user_response = getCharFromStdin() if user_response == "i": return self.TestResult.SuccessOrIgnored @@ -787,7 +819,7 @@ class SolidityLSPTestSuite: # {{{ in the test path (test/libsolidity/lsp). """ with open(self.get_test_file_path(test_case_name), mode="r", encoding="utf-8", newline='') as f: - return f.read() + return f.read().replace("\r\n", "\n") def require_params_for_method(self, method_name: str, message: dict) -> Any: """ @@ -1059,7 +1091,7 @@ class SolidityLSPTestSuite: # {{{ """ while True: print("(u)pdate/(r)etry/(s)kip file?") - user_response = sys.stdin.read(1) + user_response = getCharFromStdin() if user_response == "u": while True: try: @@ -1068,8 +1100,8 @@ class SolidityLSPTestSuite: # {{{ # pragma pylint: disable=broad-except except Exception as e: print(e) - if ret := self.user_interaction_failed_autoupdate(test): - return ret + if self.user_interaction_failed_autoupdate(test): + return True elif user_response == 's': return True elif user_response == 'r': @@ -1077,7 +1109,7 @@ class SolidityLSPTestSuite: # {{{ def user_interaction_failed_autoupdate(self, test): print("(e)dit/(r)etry/(s)kip file?") - user_response = sys.stdin.read(1) + user_response = getCharFromStdin() if user_response == "r": print("retrying...") # pragma pylint: disable=no-member @@ -1142,7 +1174,7 @@ class SolidityLSPTestSuite: # {{{ marker = self.get_file_tags("lib")["@diagnostics"] self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker) - @functools.lru_cache # pragma pylint: disable=lru-cache-decorating-method + @functools.lru_cache() # pragma pylint: disable=lru-cache-decorating-method def get_file_tags(self, test_name: str, verbose=False): """ Finds all tags (e.g. @tagname) in the given test and returns them as a @@ -1653,10 +1685,8 @@ class SolidityLSPTestSuite: # {{{ # }}} # }}} + if __name__ == "__main__": - # Turn off user input buffering so we get the input immediately, - # not only after a line break - tty.setcbreak(sys.stdin.fileno()) suite = SolidityLSPTestSuite() exit_code = suite.main() sys.exit(exit_code) From 02dfeb5427e790e8ee06c89fc267f7cdb5ab6370 Mon Sep 17 00:00:00 2001 From: Marenz Date: Mon, 25 Apr 2022 17:03:31 +0200 Subject: [PATCH 19/21] lsp.py: Trigger fatal error when importing outside of test dir --- test/lsp.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/lsp.py b/test/lsp.py index 3217fb79b..c14fc6a4c 100755 --- a/test/lsp.py +++ b/test/lsp.py @@ -536,6 +536,10 @@ class FileTestRunner: self.suite.open_file_and_wait_for_diagnostics(self.solc, self.test_name) for diagnostics in published_diagnostics: + if not diagnostics["uri"].startswith(self.suite.project_root_uri + "/"): + raise Exception( + f"'{self.test_name}.sol' imported file outside of test directory: '{diagnostics['uri']}'" + ) self.open_tests.append(diagnostics["uri"].replace(self.suite.project_root_uri + "/", "")[:-len(".sol")]) self.suite.expect_equal( @@ -570,8 +574,7 @@ class FileTestRunner: marker=markers[expected_diagnostic.marker] ) - except Exception as e: - print(e) + except Exception: self.close_all_open_files() raise From 893122eb89dc748cd6875c6b58fd82b038f18cfb Mon Sep 17 00:00:00 2001 From: Marenz Date: Wed, 27 Apr 2022 15:22:26 +0200 Subject: [PATCH 20/21] lsp.py: Support subdirectories --- test/libsolidity/lsp/didOpen_with_import.sol | 2 +- .../lsp/{ => goto}/goto_definition.sol | 0 .../{ => goto}/goto_definition_imports.sol | 0 test/libsolidity/lsp/{ => goto}/lib.sol | 0 .../lsp/{ => goto}/publish_diagnostics_1.sol | 0 .../lsp/{ => goto}/publish_diagnostics_2.sol | 0 test/lsp.py | 217 ++++++++++-------- 7 files changed, 127 insertions(+), 92 deletions(-) rename test/libsolidity/lsp/{ => goto}/goto_definition.sol (100%) rename test/libsolidity/lsp/{ => goto}/goto_definition_imports.sol (100%) rename test/libsolidity/lsp/{ => goto}/lib.sol (100%) rename test/libsolidity/lsp/{ => goto}/publish_diagnostics_1.sol (100%) rename test/libsolidity/lsp/{ => goto}/publish_diagnostics_2.sol (100%) diff --git a/test/libsolidity/lsp/didOpen_with_import.sol b/test/libsolidity/lsp/didOpen_with_import.sol index a335df759..93880e2cb 100644 --- a/test/libsolidity/lsp/didOpen_with_import.sol +++ b/test/libsolidity/lsp/didOpen_with_import.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.0; -import './lib.sol'; +import './goto/lib.sol'; contract C { diff --git a/test/libsolidity/lsp/goto_definition.sol b/test/libsolidity/lsp/goto/goto_definition.sol similarity index 100% rename from test/libsolidity/lsp/goto_definition.sol rename to test/libsolidity/lsp/goto/goto_definition.sol diff --git a/test/libsolidity/lsp/goto_definition_imports.sol b/test/libsolidity/lsp/goto/goto_definition_imports.sol similarity index 100% rename from test/libsolidity/lsp/goto_definition_imports.sol rename to test/libsolidity/lsp/goto/goto_definition_imports.sol diff --git a/test/libsolidity/lsp/lib.sol b/test/libsolidity/lsp/goto/lib.sol similarity index 100% rename from test/libsolidity/lsp/lib.sol rename to test/libsolidity/lsp/goto/lib.sol diff --git a/test/libsolidity/lsp/publish_diagnostics_1.sol b/test/libsolidity/lsp/goto/publish_diagnostics_1.sol similarity index 100% rename from test/libsolidity/lsp/publish_diagnostics_1.sol rename to test/libsolidity/lsp/goto/publish_diagnostics_1.sol diff --git a/test/libsolidity/lsp/publish_diagnostics_2.sol b/test/libsolidity/lsp/goto/publish_diagnostics_2.sol similarity index 100% rename from test/libsolidity/lsp/publish_diagnostics_2.sol rename to test/libsolidity/lsp/goto/publish_diagnostics_2.sol diff --git a/test/lsp.py b/test/lsp.py index c14fc6a4c..9a4335d73 100755 --- a/test/lsp.py +++ b/test/lsp.py @@ -82,6 +82,16 @@ TAG_REGEXES = TagRegexesTuple( re.compile(R"\^(?P[()]{1,2}) (?P@\w+)$") ) +def split_path(path): + """ + Return the test name and the subdir path of the given path. + """ + sub_dir_separator = path.find("/") + + if sub_dir_separator == -1: + return (path, None) + + return (path[sub_dir_separator+1:], path[:sub_dir_separator]) def count_index(lines, start=0): """ @@ -419,11 +429,11 @@ class TestParser: # Parse request header requestResult = TEST_REGEXES.sendRequest.match(self.current_line()) - if requestResult is not None: - ret["method"] = requestResult.group("method") - ret["request"] = "{\n" - else: - raise TestParserException(ret, "Method for request not found") + if requestResult is None: + raise TestParserException(ret, "Method for request not found on line " + self.current_line()) + + ret["method"] = requestResult.group("method") + ret["request"] = "{\n" self.next_line() @@ -505,13 +515,14 @@ class FileTestRunner: SuccessOrIgnored = auto() Reparse = auto() - def __init__(self, test_name, solc, suite): + def __init__(self, test_name, sub_dir, solc, suite): self.test_name = test_name + self.sub_dir = sub_dir self.suite = suite self.solc = solc self.open_tests = [] - self.content = self.suite.get_test_file_contents(self.test_name) - self.markers = self.suite.get_file_tags(self.test_name) + self.content = self.suite.get_test_file_contents(self.test_name, self.sub_dir) + self.markers = self.suite.get_file_tags(self.test_name, self.sub_dir) self.parsed_testcases = None self.expected_diagnostics = None @@ -533,7 +544,7 @@ class FileTestRunner: tests[self.test_name] = [] published_diagnostics = \ - self.suite.open_file_and_wait_for_diagnostics(self.solc, self.test_name) + self.suite.open_file_and_wait_for_diagnostics(self.solc, self.test_name, self.sub_dir) for diagnostics in published_diagnostics: if not diagnostics["uri"].startswith(self.suite.project_root_uri + "/"): @@ -548,7 +559,8 @@ class FileTestRunner: description="Amount of reports does not match!") for diagnostics in published_diagnostics: - testname = diagnostics["uri"].replace(self.suite.project_root_uri + "/", "")[:-len(".sol")] + testname_and_subdir = diagnostics["uri"].replace(self.suite.project_root_uri + "/", "")[:-len(".sol")] + testname, sub_dir = split_path(testname_and_subdir) expected_diagnostics = tests[testname] self.suite.expect_equal( @@ -556,7 +568,7 @@ class FileTestRunner: len(expected_diagnostics), description="Unexpected amount of diagnostics" ) - markers = self.suite.get_file_tags(testname) + markers = self.suite.get_file_tags(testname, sub_dir) for actual_diagnostic in diagnostics["diagnostics"]: expected_diagnostic = next((diagnostic for diagnostic in expected_diagnostics if actual_diagnostic['range'] == @@ -579,10 +591,12 @@ class FileTestRunner: raise def close_all_open_files(self): - for test in self.open_tests: + for testpath in self.open_tests: + test, sub_dir = split_path(testpath) + self.solc.send_message( 'textDocument/didClose', - { 'textDocument': { 'uri': self.suite.get_test_file_uri(test) }} + { 'textDocument': { 'uri': self.suite.get_test_file_uri(test, sub_dir) }} ) self.suite.wait_for_diagnostics(self.solc) @@ -612,13 +626,13 @@ class FileTestRunner: self.close_all_open_files() def user_interaction_failed_method_test(self, testcase, actual, expected): - actual_pretty = self.suite.replace_ranges_with_tags(actual) + actual_pretty = self.suite.replace_ranges_with_tags(actual, self.sub_dir) if expected is None: print("Failed to parse expected response, received:\n" + actual) else: print("Expected:\n" + \ - self.suite.replace_ranges_with_tags(expected) + \ + self.suite.replace_ranges_with_tags(expected, self.sub_dir) + \ "\nbut got:\n" + actual_pretty ) @@ -631,10 +645,16 @@ class FileTestRunner: if user_response == "u": actual = actual["result"] self.content = self.content[:testcase.responseBegin] + \ - prepend_comments("<- " + self.suite.replace_ranges_with_tags(actual)) + \ + prepend_comments( + "<- " + \ + self.suite.replace_ranges_with_tags(actual, self.sub_dir)) + \ self.content[testcase.responseEnd:] - with open(self.suite.get_test_file_path(self.test_name), mode="w", encoding="utf-8", newline='') as f: + with open(self.suite.get_test_file_path(\ + self.test_name, self.sub_dir), \ + mode="w", \ + encoding="utf-8", \ + newline='') as f: f.write(self.content) return self.TestResult.Reparse if user_response == "r": @@ -650,12 +670,13 @@ class FileTestRunner: requestBodyJson = self.parse_json_with_tags(testcase.request, self.markers) # add textDocument/uri if missing if 'textDocument' not in requestBodyJson: - requestBodyJson['textDocument'] = { 'uri': self.suite.get_test_file_uri(self.test_name) } + requestBodyJson['textDocument'] = { 'uri': self.suite.get_test_file_uri(self.test_name, self.sub_dir) } actualResponseJson = self.solc.call_method(testcase.method, requestBodyJson) # simplify response for result in actualResponseJson["result"]: - result["uri"] = result["uri"].replace(self.suite.project_root_uri + "/", "") + if "uri" in result: + result["uri"] = result["uri"].replace(self.suite.project_root_uri + "/" + self.sub_dir + "/", "") if "jsonrpc" in actualResponseJson: actualResponseJson.pop("jsonrpc") @@ -692,11 +713,14 @@ class FileTestRunner: replace_tag(el, markers) return data + if not isinstance(data, dict): + return data + # Check if we need markers from a specific file # Needs to be done before the loop or it might be called only after # we found "range" or "position" if "uri" in data: - markers = self.suite.get_file_tags(data["uri"][:-len(".sol")]) + markers = self.suite.get_file_tags(data["uri"][:-len(".sol")], self.sub_dir) for key, val in data.items(): if key == "range": @@ -809,19 +833,21 @@ class SolidityLSPTestSuite: # {{{ lsp.send_message("$/setTrace", { 'value': 'messages' }) # {{{ helpers - def get_test_file_path(self, test_case_name): + def get_test_file_path(self, test_case_name, sub_dir=None): + if sub_dir: + return f"{self.project_root_dir}/{sub_dir}/{test_case_name}.sol" return f"{self.project_root_dir}/{test_case_name}.sol" - def get_test_file_uri(self, test_case_name): - return PurePath(self.get_test_file_path(test_case_name)).as_uri() + def get_test_file_uri(self, test_case_name, sub_dir=None): + return PurePath(self.get_test_file_path(test_case_name, sub_dir)).as_uri() - def get_test_file_contents(self, test_case_name): + def get_test_file_contents(self, test_case_name, sub_dir=None): """ Reads the file contents from disc for a given test case. The `test_case_name` will be the basename of the file - in the test path (test/libsolidity/lsp). + in the test path (test/libsolidity/lsp/{sub_dir}). """ - with open(self.get_test_file_path(test_case_name), mode="r", encoding="utf-8", newline='') as f: + with open(self.get_test_file_path(test_case_name, sub_dir), mode="r", encoding="utf-8", newline='') as f: return f.read().replace("\r\n", "\n") def require_params_for_method(self, method_name: str, message: dict) -> Any: @@ -863,13 +889,13 @@ class SolidityLSPTestSuite: # {{{ return sorted(reports, key=lambda x: x['uri']) - def fetch_and_format_diagnostics(self, solc: JsonRpcProcess, test): + def fetch_and_format_diagnostics(self, solc: JsonRpcProcess, test, sub_dir=None): expectations = "" - published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, test) + published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, test, sub_dir) for diagnostics in published_diagnostics: - testname = diagnostics["uri"].replace(self.project_root_uri + "/", "")[:-len(".sol")] + testname = diagnostics["uri"].replace(f"{self.project_root_uri}/{sub_dir}/", "")[:-len(".sol")] # Skip empty diagnostics within the same file if len(diagnostics["diagnostics"]) == 0 and testname == test: @@ -878,7 +904,7 @@ class SolidityLSPTestSuite: # {{{ expectations += f"// {testname}:" for diagnostic in diagnostics["diagnostics"]: - tag = self.find_tag_with_range(testname, diagnostic['range']) + tag = self.find_tag_with_range(testname, sub_dir, diagnostic['range']) if tag is None: raise Exception(f"No tag found for diagnostic range {diagnostic['range']}") @@ -892,6 +918,7 @@ class SolidityLSPTestSuite: # {{{ self, solc: JsonRpcProcess, test, + sub_dir, content, current_diagnostics: TestParser.Diagnostics ): @@ -902,10 +929,10 @@ class SolidityLSPTestSuite: # {{{ content = content[:current_diagnostics.start] + \ test_header + \ - self.fetch_and_format_diagnostics(solc, test) + \ + self.fetch_and_format_diagnostics(solc, test, sub_dir) + \ content[current_diagnostics.end:] - with open(self.get_test_file_path(test), mode="w", encoding="utf-8", newline='') as f: + with open(self.get_test_file_path(test, sub_dir), mode="w", encoding="utf-8", newline='') as f: f.write(content) return content @@ -914,6 +941,7 @@ class SolidityLSPTestSuite: # {{{ self, solc_process: JsonRpcProcess, test_case_name: str, + sub_dir=None ) -> List[Any]: """ Opens file for given test case and waits for diagnostics to be published. @@ -923,10 +951,10 @@ class SolidityLSPTestSuite: # {{{ { 'textDocument': { - 'uri': self.get_test_file_uri(test_case_name), + 'uri': self.get_test_file_uri(test_case_name, sub_dir), 'languageId': 'Solidity', 'version': 1, - 'text': self.get_test_file_contents(test_case_name) + 'text': self.get_test_file_contents(test_case_name, sub_dir) } } ) @@ -1041,12 +1069,12 @@ class SolidityLSPTestSuite: # {{{ self.expect_location(response['result'][0], expected_uri, expected_lineNo, expected_startEndColumns) - def find_tag_with_range(self, test, target_range): + def find_tag_with_range(self, test, sub_dir, target_range): """ Find and return the tag that represents the requested range otherwise return None. """ - markers = self.get_file_tags(test) + markers = self.get_file_tags(test, sub_dir) for tag, tag_range in markers.items(): if tag_range == target_range: @@ -1054,7 +1082,7 @@ class SolidityLSPTestSuite: # {{{ return None - def replace_ranges_with_tags(self, content): + def replace_ranges_with_tags(self, content, sub_dir): """ Replace matching ranges with "@". """ @@ -1070,7 +1098,7 @@ class SolidityLSPTestSuite: # {{{ for item in recursive_iter(content): if "uri" in item and "range" in item: - markers = self.get_file_tags(item["uri"][:-len(".sol")]) + markers = self.get_file_tags(item["uri"][:-len(".sol")], sub_dir) for tag, tagRange in markers.items(): if tagRange == item["range"]: item["range"] = str(tag) @@ -1085,6 +1113,7 @@ class SolidityLSPTestSuite: # {{{ self, solc: JsonRpcProcess, test, + sub_dir, content, current_diagnostics: TestParser.Diagnostics ): @@ -1098,19 +1127,19 @@ class SolidityLSPTestSuite: # {{{ if user_response == "u": while True: try: - self.update_diagnostics_in_file(solc, test, content, current_diagnostics) + self.update_diagnostics_in_file(solc, test, sub_dir, content, current_diagnostics) return False # pragma pylint: disable=broad-except except Exception as e: print(e) - if self.user_interaction_failed_autoupdate(test): + if self.user_interaction_failed_autoupdate(test, sub_dir): return True elif user_response == 's': return True elif user_response == 'r': return False - def user_interaction_failed_autoupdate(self, test): + def user_interaction_failed_autoupdate(self, test, sub_dir): print("(e)dit/(r)etry/(s)kip file?") user_response = getCharFromStdin() if user_response == "r": @@ -1121,7 +1150,7 @@ class SolidityLSPTestSuite: # {{{ if user_response == "e": editor = os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')) subprocess.run( - f'{editor} {self.get_test_file_path(test)}', + f'{editor} {self.get_test_file_path(test, sub_dir)}', shell=True, check=True ) @@ -1170,15 +1199,15 @@ class SolidityLSPTestSuite: # {{{ self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") self.expect_equal(len(report['diagnostics']), 0, "no diagnostics") - # imported file (./lib.sol): + # imported file (goto/lib.sol): report = published_diagnostics[1] - self.expect_equal(report['uri'], self.get_test_file_uri('lib'), "Correct file URI") + self.expect_equal(report['uri'], self.get_test_file_uri('lib', 'goto'), "Correct file URI") self.expect_equal(len(report['diagnostics']), 1, "one diagnostic") - marker = self.get_file_tags("lib")["@diagnostics"] + marker = self.get_file_tags("lib", "goto")["@diagnostics"] self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker) @functools.lru_cache() # pragma pylint: disable=lru-cache-decorating-method - def get_file_tags(self, test_name: str, verbose=False): + def get_file_tags(self, test_name: str, sub_dir=None, verbose=False): """ Finds all tags (e.g. @tagname) in the given test and returns them as a dictionary having the following structure: { @@ -1188,7 +1217,7 @@ class SolidityLSPTestSuite: # {{{ } } """ - content = self.get_test_file_contents(test_name) + content = self.get_test_file_contents(test_name, sub_dir) markers = {} @@ -1223,14 +1252,14 @@ class SolidityLSPTestSuite: # {{{ def test_didChange_in_A_causing_error_in_B(self, solc: JsonRpcProcess) -> None: # Reusing another test but now change some file that generates an error in the other. self.test_textDocument_didOpen_with_relative_import(solc) - marker = self.get_file_tags("lib")["@addFunction"] - self.open_file_and_wait_for_diagnostics(solc, 'lib') + marker = self.get_file_tags("lib", "goto")["@addFunction"] + self.open_file_and_wait_for_diagnostics(solc, 'lib', "goto") solc.send_message( 'textDocument/didChange', { 'textDocument': { - 'uri': self.get_test_file_uri('lib') + 'uri': self.get_test_file_uri('lib', 'goto') }, 'contentChanges': [ @@ -1254,7 +1283,7 @@ class SolidityLSPTestSuite: # {{{ # The modified file retains the same diagnostics. report = published_diagnostics[1] - self.expect_equal(report['uri'], self.get_test_file_uri('lib')) + self.expect_equal(report['uri'], self.get_test_file_uri('lib', 'goto')) self.expect_equal(len(report['diagnostics']), 0) # The warning went away because the compiler aborts further processing after the error. @@ -1276,58 +1305,64 @@ class SolidityLSPTestSuite: # {{{ self.expect_equal(report['uri'], self.get_test_file_uri(main_file_name), "Correct file URI") self.expect_equal(len(report['diagnostics']), 0, "one diagnostic") - # imported file (./lib.sol): + # imported file (./goto/lib.sol): report = published_diagnostics[1] - self.expect_equal(report['uri'], self.get_test_file_uri('lib'), "Correct file URI") + self.expect_equal(report['uri'], self.get_test_file_uri('lib', 'goto'), "Correct file URI") self.expect_equal(len(report['diagnostics']), 1, "one diagnostic") - markers = self.get_file_tags('lib') + markers = self.get_file_tags('lib', 'goto') marker = markers["@diagnostics"] self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker) def test_generic(self, solc: JsonRpcProcess) -> None: self.setup_lsp(solc) - STATIC_TESTS = ['didChange_template', 'didOpen_with_import', 'publish_diagnostics_3'] - - tests = filter( - lambda x: x not in STATIC_TESTS, - map(lambda x: x[:-len(".sol")], os.listdir(self.project_root_dir)) + sub_dirs = filter( + lambda filepath: filepath.is_dir(), + os.scandir(self.project_root_dir) ) - for test in tests: - try_again = True - print(f"Running test {test}") + for sub_dir in map(lambda filepath: filepath.name, sub_dirs): + tests = map( + lambda filename: filename[:-len(".sol")], + os.listdir(f"{self.project_root_dir}/{sub_dir}") + ) - while try_again: - runner = FileTestRunner(test, solc, self) + print(f"Running tests in subdirectory '{sub_dir}'...") + for test in tests: + try_again = True + print(f"\t{test}") - try: - runner.test_diagnostics() - try_again = not runner.test_methods() - except ExpectationFailed as e: - print(e) + while try_again: + runner = FileTestRunner(test, sub_dir, solc, self) - if e.part == e.Part.Diagnostics: - try_again = not self.user_interaction_failed_diagnostics( - solc, - test, - runner.content, - runner.expected_diagnostics - ) - else: - raise + try: + runner.test_diagnostics() + try_again = not runner.test_methods() + except ExpectationFailed as e: + print(e) + + if e.part == e.Part.Diagnostics: + try_again = not self.user_interaction_failed_diagnostics( + solc, + test, + sub_dir, + runner.content, + runner.expected_diagnostics + ) + else: + raise def test_textDocument_didChange_updates_diagnostics(self, solc: JsonRpcProcess) -> None: self.setup_lsp(solc) TEST_NAME = 'publish_diagnostics_1' - published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME) + published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME, "goto") self.expect_equal(len(published_diagnostics), 1, "One published_diagnostics message") report = published_diagnostics[0] - self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") + self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME, "goto"), "Correct file URI") diagnostics = report['diagnostics'] self.expect_equal(len(diagnostics), 3, "3 diagnostic messages") - markers = self.get_file_tags(TEST_NAME) + markers = self.get_file_tags(TEST_NAME, "goto") self.expect_diagnostic(diagnostics[0], code=6321, marker=markers["@unusedReturnVariable"]) self.expect_diagnostic(diagnostics[1], code=2072, marker=markers["@unusedVariable"]) self.expect_diagnostic(diagnostics[2], code=2072, marker=markers["@unusedContractVariable"]) @@ -1336,7 +1371,7 @@ class SolidityLSPTestSuite: # {{{ 'textDocument/didChange', { 'textDocument': { - 'uri': self.get_test_file_uri(TEST_NAME) + 'uri': self.get_test_file_uri(TEST_NAME, "goto") }, 'contentChanges': [ { @@ -1349,7 +1384,7 @@ class SolidityLSPTestSuite: # {{{ published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1) report = published_diagnostics[0] - self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") + self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME, "goto"), "Correct file URI") diagnostics = report['diagnostics'] self.expect_equal(len(diagnostics), 2) self.expect_diagnostic(diagnostics[0], code=6321, marker=markers["@unusedReturnVariable"]) @@ -1358,9 +1393,9 @@ class SolidityLSPTestSuite: # {{{ def test_textDocument_didChange_delete_line_and_close(self, solc: JsonRpcProcess) -> None: # Reuse this test to prepare and ensure it is as expected self.test_textDocument_didOpen_with_relative_import(solc) - self.open_file_and_wait_for_diagnostics(solc, 'lib') + self.open_file_and_wait_for_diagnostics(solc, 'lib', 'goto') - marker = self.get_file_tags('lib')["@diagnostics"] + marker = self.get_file_tags('lib', 'goto')["@diagnostics"] # lib.sol: Fix the unused variable message by removing it. solc.send_message( @@ -1368,7 +1403,7 @@ class SolidityLSPTestSuite: # {{{ { 'textDocument': { - 'uri': self.get_test_file_uri('lib') + 'uri': self.get_test_file_uri('lib', 'goto') }, 'contentChanges': # delete the in-body statement: `uint unused;` [ @@ -1385,13 +1420,13 @@ class SolidityLSPTestSuite: # {{{ self.expect_equal(report1['uri'], self.get_test_file_uri('didOpen_with_import'), "Correct file URI") self.expect_equal(len(report1['diagnostics']), 0, "no diagnostics in didOpen_with_import.sol") report2 = published_diagnostics[1] - self.expect_equal(report2['uri'], self.get_test_file_uri('lib'), "Correct file URI") + self.expect_equal(report2['uri'], self.get_test_file_uri('lib', 'goto'), "Correct file URI") self.expect_equal(len(report2['diagnostics']), 0, "no diagnostics in lib.sol") # Now close the file and expect the warning to re-appear solc.send_message( 'textDocument/didClose', - { 'textDocument': { 'uri': self.get_test_file_uri('lib') }} + { 'textDocument': { 'uri': self.get_test_file_uri('lib', 'goto') }} ) published_diagnostics = self.wait_for_diagnostics(solc) @@ -1483,14 +1518,14 @@ class SolidityLSPTestSuite: # {{{ 'text': '// SPDX-License-Identifier: UNLICENSED\n' 'pragma solidity >=0.8.0;\n' - 'import "./lib.sol";\n' + 'import "./goto/lib.sol";\n' } }) reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 2, '') self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") - marker = self.get_file_tags("lib")["@diagnostics"] + marker = self.get_file_tags("lib", 'goto')["@diagnostics"] # unused variable in lib.sol self.expect_diagnostic(reports[1]['diagnostics'][0], code=2072, marker=marker) @@ -1502,7 +1537,7 @@ class SolidityLSPTestSuite: # {{{ ) reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 1, '') - self.expect_equal(reports[0]['uri'], f'{self.project_root_uri}/lib.sol', "") + self.expect_equal(reports[0]['uri'], f'{self.project_root_uri}/goto/lib.sol', "") self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") def test_textDocument_didChange_at_eol(self, solc: JsonRpcProcess) -> None: From 0e0d1972f9522815f8d459d482574bafac5f66b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Tue, 10 May 2022 12:43:27 +0200 Subject: [PATCH 21/21] Disable non-deterministic counterexamples in some SMT tests - The counterexamples sometimes do appear and the tests fail. --- .../smtCheckerTests/blockchain_state/balance_receive_4.sol | 1 + test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol | 1 + 2 files changed, 2 insertions(+) diff --git a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol index d72da3efa..cd6e1ef9b 100644 --- a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol +++ b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol @@ -11,6 +11,7 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // SMTIgnoreOS: macos // ---- // Warning 4984: (82-85): CHC: Overflow (resulting value larger than 2**256 - 1) might happen here. diff --git a/test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol b/test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol index ced7e37c4..699d22097 100644 --- a/test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol +++ b/test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol @@ -18,6 +18,7 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- // Warning 6328: (407-457): CHC: Assertion violation happens here. // Info 1180: Contract invariant(s) for :C:\n(true || true || true)\nReentrancy property(ies) for :C:\n((( = 0) && ((:var 0) = (:var 1))) || true || true || true || true)\n(true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true)\n = 0 -> no errors\n = 1 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(1))) == 1)\n = 2 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(2))) == 2)\n = 3 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 0xff)\n = 4 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 1)\n