mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #12423 from ethereum/develop
Merge develop into breaking.
This commit is contained in:
commit
923d1cf2d2
@ -198,6 +198,19 @@ defaults:
|
||||
- store_artifacts: *artifacts_test_results
|
||||
- gitter_notify_failure_unless_pr
|
||||
|
||||
- steps_test_lsp: &steps_test_lsp
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: pip install --user deepdiff colorama
|
||||
- run:
|
||||
name: Executing solc LSP test suite
|
||||
command: ./test/lsp.py ./build/solc/solc
|
||||
- gitter_notify_failure_unless_pr
|
||||
|
||||
- steps_soltest_all: &steps_soltest_all
|
||||
steps:
|
||||
- checkout
|
||||
@ -519,7 +532,7 @@ jobs:
|
||||
command: apt -q update && apt install -y python3-pip
|
||||
- run:
|
||||
name: Install pylint
|
||||
command: python3 -m pip install pylint z3-solver pygments-lexer-solidity parsec tabulate
|
||||
command: python3 -m pip install pylint z3-solver pygments-lexer-solidity parsec tabulate deepdiff colorama
|
||||
# also z3-solver, parsec and tabulate to make sure pylint knows about this module, pygments-lexer-solidity for docs
|
||||
- run:
|
||||
name: Linting Python Scripts
|
||||
@ -887,6 +900,10 @@ jobs:
|
||||
parallelism: 15 # 7 EVM versions, each with/without optimization + 1 ABIv1/@nooptions run
|
||||
<<: *steps_soltest_all
|
||||
|
||||
t_ubu_lsp: &t_ubu_lsp
|
||||
<<: *base_ubuntu2004_small
|
||||
<<: *steps_test_lsp
|
||||
|
||||
t_archlinux_soltest: &t_archlinux_soltest
|
||||
<<: *base_archlinux
|
||||
environment:
|
||||
@ -1288,6 +1305,7 @@ workflows:
|
||||
- t_ubu_soltest_enforce_yul: *workflow_ubuntu2004
|
||||
- b_ubu_clang: *workflow_trigger_on_tags
|
||||
- t_ubu_clang_soltest: *workflow_ubuntu2004_clang
|
||||
- t_ubu_lsp: *workflow_ubuntu2004
|
||||
|
||||
# Ubuntu fake release build and tests
|
||||
- b_ubu_release: *workflow_trigger_on_tags
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,6 +1,5 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Bug reports about the Solidity Compiler.
|
||||
---
|
||||
|
||||
<!--## Prerequisites
|
||||
@ -14,7 +13,6 @@ about: Bug reports about the Solidity Compiler.
|
||||
- The issue should be reproducible with the latest solidity version; however, this isn't a hard requirement and being reproducible with an older version is sufficient.
|
||||
|
||||
*Delete the above section and the instructions in the sections below before submitting*
|
||||
|
||||
-->
|
||||
|
||||
## Description
|
||||
|
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Bug Report
|
||||
url: https://github.com/ethereum/solidity/issues/new?template=bug_report.md&projects=ethereum/solidity/43&labels=bug+%3Abug%3A
|
||||
about: Bug reports about the Solidity Compiler.
|
||||
- name: Documentation Issue
|
||||
url: https://github.com/ethereum/solidity/issues/new?template=documentation_issue.md&projects=ethereum/solidity/43&labels=documentation+%3Abook%3A
|
||||
about: Solidity documentation.
|
||||
- name: Feature Request
|
||||
url: https://github.com/ethereum/solidity/issues/new?template=feature_request.md&projects=ethereum/solidity/43&labels=feature
|
||||
about: Solidity language or infrastructure feature requests.
|
||||
- name: Report a security vulnerability
|
||||
url: https://github.com/ethereum/solidity/security/policy
|
||||
about: Please review our security policy for more details.
|
13
.github/ISSUE_TEMPLATE/documentation_issue.md
vendored
13
.github/ISSUE_TEMPLATE/documentation_issue.md
vendored
@ -1,22 +1,15 @@
|
||||
---
|
||||
name: Documentation Issue
|
||||
about: Solidity documentation.
|
||||
---
|
||||
|
||||
## Page
|
||||
|
||||
<!--
|
||||
Please link directly to the page which you think has a problem
|
||||
-->
|
||||
<!--Please link directly to the page which you think has a problem.-->
|
||||
|
||||
## Abstract
|
||||
|
||||
<!--
|
||||
Please describe in detail what is wrong.
|
||||
-->
|
||||
<!--Please describe in detail what is wrong.-->
|
||||
|
||||
## Pull request
|
||||
|
||||
<!--
|
||||
Please link to your pull request which resolves this issue
|
||||
-->
|
||||
<!--Please link to your pull request which resolves this issue.-->
|
||||
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,6 +1,5 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Solidity language or infrastructure feature requests.
|
||||
---
|
||||
|
||||
<!--## Prerequisites
|
||||
@ -13,35 +12,24 @@ about: Solidity language or infrastructure feature requests.
|
||||
- Ensure the issue isn't already reported (check `feature` and `language design` labels).
|
||||
|
||||
*Delete the above section and the instructions in the sections below before submitting*
|
||||
|
||||
-->
|
||||
|
||||
## Abstract
|
||||
|
||||
<!--
|
||||
Please describe by example what problem you see in the current Solidity language
|
||||
and reason about it.
|
||||
-->
|
||||
<!--Please describe by example what problem you see in the current Solidity language and reason about it.-->
|
||||
|
||||
## Motivation
|
||||
|
||||
<!--
|
||||
In this section you describe how you propose to address the problem you described earlier,
|
||||
including by giving one or more exemplary source code snippets for demonstration.
|
||||
-->
|
||||
<!--In this section you describe how you propose to address the problem you described earlier, including by giving one or more exemplary source code snippets for demonstration.-->
|
||||
|
||||
## Specification
|
||||
|
||||
<!--
|
||||
The technical specification should describe the syntax and semantics of any new feature. The
|
||||
specification should be detailed enough to allow any developer to implement the functionality.
|
||||
-->
|
||||
<!--The technical specification should describe the syntax and semantics of any new feature. The specification should be detailed enough to allow any developer to implement the functionality.-->
|
||||
|
||||
## Backwards Compatibility
|
||||
|
||||
<!--
|
||||
All language changes that introduce backwards incompatibilities must include a section describing
|
||||
these incompatibilities and their severity.
|
||||
All language changes that introduce backwards incompatibilities must include a section describing these incompatibilities and their severity.
|
||||
|
||||
Please describe how you propose to deal with these incompatibilities.
|
||||
-->
|
0
.github/ISSUE_TEMPLATE/general.md
vendored
0
.github/ISSUE_TEMPLATE/general.md
vendored
@ -11,9 +11,11 @@ Breaking changes:
|
||||
### 0.8.11 (unreleased)
|
||||
|
||||
Language Features:
|
||||
* General: New builtin function ``abi.encodeCall(functionPointer, (arg1, arg2, ...))`` that type-checks the arguments and returns the ABI-encoded function call data.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* Commandline Interface: Add ``--lsp`` option to get ``solc`` to act as a Language Server (LSP) communicating over stdio.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
@ -21,7 +23,7 @@ Bugfixes:
|
||||
* SMTChecker: Fix internal error when an unsafe target is solved more than once and the counterexample messages are different.
|
||||
* SMTChecker: Fix soundness of assigned storage/memory local pointers that were not erasing enough knowledge.
|
||||
* Fix internal error when a function has a calldata struct argument with an internal type inside.
|
||||
|
||||
* IR Generator: Fix IR syntax error when copying storage arrays of functions.
|
||||
|
||||
|
||||
### 0.8.10 (2021-11-09)
|
||||
|
@ -80,6 +80,8 @@ Global Variables
|
||||
the given arguments. Note that this encoding can be ambiguous!
|
||||
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes
|
||||
the given arguments starting from the second and prepends the given four-byte selector
|
||||
- ``abi.encodeCall(function functionPointer, (...)) returns (bytes memory)``: ABI-encodes a call to ``functionPointer`` with the arguments found in the
|
||||
tuple. Performs a full type-check, ensuring the types match the function signature. Result equals ``abi.encodeWithSelector(functionPointer.selector, (...))``
|
||||
- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent
|
||||
to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)``
|
||||
- ``bytes.concat(...) returns (bytes memory)``: :ref:`Concatenates variable number of
|
||||
|
@ -648,7 +648,7 @@ in the following situations:
|
||||
#. If your contract receives Ether via a public getter function.
|
||||
|
||||
For the following cases, the error data from the external call
|
||||
(if provided) is forwarded. This mean that it can either cause
|
||||
(if provided) is forwarded. This means that it can either cause
|
||||
an `Error` or a `Panic` (or whatever else was given):
|
||||
|
||||
#. If a ``.transfer()`` fails.
|
||||
@ -718,7 +718,7 @@ The ``revert`` statement takes a custom error as direct argument without parenth
|
||||
|
||||
revert CustomError(arg1, arg2);
|
||||
|
||||
For backards-compatibility reasons, there is also the ``revert()`` function, which uses parentheses
|
||||
For backwards-compatibility reasons, there is also the ``revert()`` function, which uses parentheses
|
||||
and accepts a string:
|
||||
|
||||
revert();
|
||||
@ -863,7 +863,7 @@ type of error:
|
||||
|
||||
|
||||
It is planned to support other types of error data in the future.
|
||||
The strings ``Error`` and ``Panic`` are currently parsed as is and are not treated as an identifiers.
|
||||
The strings ``Error`` and ``Panic`` are currently parsed as is and are not treated as identifiers.
|
||||
|
||||
In order to catch all error cases, you have to have at least the clause
|
||||
``catch { ...}`` or the clause ``catch (bytes memory lowLevelData) { ... }``.
|
||||
|
@ -1,7 +1,35 @@
|
||||
.. index:: assignment, ! delete, lvalue
|
||||
.. index:: ! operator
|
||||
|
||||
Operators Involving LValues
|
||||
===========================
|
||||
Operators
|
||||
=========
|
||||
|
||||
Arithmetic and bit operators can be applied even if the two operands do not have the same type.
|
||||
For example, you can compute ``y = x + z``, where ``x`` is a ``uint8`` and ``z`` has
|
||||
the type ``int32``. In these cases, the following mechanism will be used to determine
|
||||
the type in which the operation is computed (this is important in case of overflow)
|
||||
and the type of the operator's result:
|
||||
|
||||
1. If the type of the right operand can be implicitly converted to the type of the left
|
||||
operand, use the type of the left operand,
|
||||
2. if the type of the left operand can be implicitly converted to the type of the right
|
||||
operand, use the type of the right operand,
|
||||
3. otherwise, the operation is not allowed.
|
||||
|
||||
In case one of the operands is a :ref:`literal number <rational_literals>` it is first converted to its
|
||||
"mobile type", which is the smallest type that can hold the value
|
||||
(unsigned types of the same bit-width are considered "smaller" than the signed types).
|
||||
If both are literal numbers, the operation is computed with arbitrary precision.
|
||||
|
||||
The operator's result type is the same as the type the operation is performed in,
|
||||
except for comparison operators where the result is always ``bool``.
|
||||
|
||||
The operators ``**`` (exponentiation), ``<<`` and ``>>`` use the type of the
|
||||
left operand for the operation and the result.
|
||||
|
||||
.. index:: assignment, lvalue, ! compound operators
|
||||
|
||||
Compound and Increment/Decrement Operators
|
||||
------------------------------------------
|
||||
|
||||
If ``a`` is an LValue (i.e. a variable or something that can be assigned to), the
|
||||
following operators are available as shorthands:
|
||||
@ -12,6 +40,8 @@ to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous valu
|
||||
of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but
|
||||
return the value after the change.
|
||||
|
||||
.. index:: !delete
|
||||
|
||||
.. _delete:
|
||||
|
||||
delete
|
||||
|
@ -136,6 +136,7 @@ ABI Encoding and Decoding Functions
|
||||
- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding <abi_packed_mode>` of the given arguments. Note that packed encoding can be ambiguous!
|
||||
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: ABI-encodes the given arguments starting from the second and prepends the given four-byte selector
|
||||
- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)``
|
||||
- ``abi.encodeCall(function functionPointer, (...)) returns (bytes memory)``: ABI-encodes a call to ``functionPointer`` with the arguments found in the tuple. Performs a full type-check, ensuring the types match the function signature. Result equals ``abi.encodeWithSelector(functionPointer.selector, (...))``
|
||||
|
||||
.. note::
|
||||
These encoding functions can be used to craft data for external function calls without actually
|
||||
|
@ -155,6 +155,12 @@ set(sources
|
||||
interface/StorageLayout.h
|
||||
interface/Version.cpp
|
||||
interface/Version.h
|
||||
lsp/LanguageServer.cpp
|
||||
lsp/LanguageServer.h
|
||||
lsp/FileRepository.cpp
|
||||
lsp/FileRepository.h
|
||||
lsp/Transport.cpp
|
||||
lsp/Transport.h
|
||||
parsing/DocStringParser.cpp
|
||||
parsing/DocStringParser.h
|
||||
parsing/Parser.cpp
|
||||
|
@ -1996,6 +1996,7 @@ void TypeChecker::typeCheckABIEncodeFunctions(
|
||||
_functionType->kind() == FunctionType::Kind::ABIEncode ||
|
||||
_functionType->kind() == FunctionType::Kind::ABIEncodePacked ||
|
||||
_functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector ||
|
||||
_functionType->kind() == FunctionType::Kind::ABIEncodeCall ||
|
||||
_functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature,
|
||||
"ABI function has unexpected FunctionType::Kind."
|
||||
);
|
||||
@ -2020,6 +2021,13 @@ void TypeChecker::typeCheckABIEncodeFunctions(
|
||||
// Perform standard function call type checking
|
||||
typeCheckFunctionGeneralChecks(_functionCall, _functionType);
|
||||
|
||||
// No further generic checks needed as we do a precise check for ABIEncodeCall
|
||||
if (_functionType->kind() == FunctionType::Kind::ABIEncodeCall)
|
||||
{
|
||||
typeCheckABIEncodeCallFunction(_functionCall);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check additional arguments for variadic functions
|
||||
vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();
|
||||
for (size_t i = 0; i < arguments.size(); ++i)
|
||||
@ -2078,6 +2086,110 @@ void TypeChecker::typeCheckABIEncodeFunctions(
|
||||
}
|
||||
}
|
||||
|
||||
void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCall)
|
||||
{
|
||||
vector<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();
|
||||
|
||||
// Expecting first argument to be the function pointer and second to be a tuple.
|
||||
if (arguments.size() != 2)
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
6219_error,
|
||||
_functionCall.location(),
|
||||
"Expected two arguments: a function pointer followed by a tuple."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const functionPointerType = dynamic_cast<FunctionTypePointer>(type(*arguments.front()));
|
||||
|
||||
if (!functionPointerType)
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
5511_error,
|
||||
arguments.front()->location(),
|
||||
"Expected first argument to be a function pointer, not \"" +
|
||||
type(*arguments.front())->canonicalName() +
|
||||
"\"."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (functionPointerType->kind() != FunctionType::Kind::External)
|
||||
{
|
||||
string msg = "Function must be \"public\" or \"external\".";
|
||||
SecondarySourceLocation ssl{};
|
||||
|
||||
if (functionPointerType->hasDeclaration())
|
||||
{
|
||||
ssl.append("Function is declared here:", functionPointerType->declaration().location());
|
||||
if (functionPointerType->declaration().scope() == m_currentContract)
|
||||
msg += " Did you forget to prefix \"this.\"?";
|
||||
}
|
||||
|
||||
m_errorReporter.typeError(3509_error, arguments[0]->location(), ssl, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
solAssert(!functionPointerType->takesArbitraryParameters(), "Function must have fixed parameters.");
|
||||
|
||||
// Tuples with only one component become that component
|
||||
vector<ASTPointer<Expression const>> callArguments;
|
||||
|
||||
auto const* tupleType = dynamic_cast<TupleType const*>(type(*arguments[1]));
|
||||
if (tupleType)
|
||||
{
|
||||
auto const& argumentTuple = dynamic_cast<TupleExpression const&>(*arguments[1].get());
|
||||
callArguments = decltype(callArguments){argumentTuple.components().begin(), argumentTuple.components().end()};
|
||||
}
|
||||
else
|
||||
callArguments.push_back(arguments[1]);
|
||||
|
||||
if (functionPointerType->parameterTypes().size() != callArguments.size())
|
||||
{
|
||||
if (tupleType)
|
||||
m_errorReporter.typeError(
|
||||
7788_error,
|
||||
_functionCall.location(),
|
||||
"Expected " +
|
||||
to_string(functionPointerType->parameterTypes().size()) +
|
||||
" instead of " +
|
||||
to_string(callArguments.size()) +
|
||||
" components for the tuple parameter."
|
||||
);
|
||||
else
|
||||
m_errorReporter.typeError(
|
||||
7515_error,
|
||||
_functionCall.location(),
|
||||
"Expected a tuple with " +
|
||||
to_string(functionPointerType->parameterTypes().size()) +
|
||||
" components instead of a single non-tuple parameter."
|
||||
);
|
||||
}
|
||||
|
||||
// Use min() to check as much as we can before failing fatally
|
||||
size_t const numParameters = min(callArguments.size(), functionPointerType->parameterTypes().size());
|
||||
|
||||
for (size_t i = 0; i < numParameters; i++)
|
||||
{
|
||||
Type const& argType = *type(*callArguments[i]);
|
||||
BoolResult result = argType.isImplicitlyConvertibleTo(*functionPointerType->parameterTypes()[i]);
|
||||
if (!result)
|
||||
m_errorReporter.typeError(
|
||||
5407_error,
|
||||
callArguments[i]->location(),
|
||||
"Cannot implicitly convert component at position " +
|
||||
to_string(i) +
|
||||
" from \"" +
|
||||
argType.canonicalName() +
|
||||
"\" to \"" +
|
||||
functionPointerType->parameterTypes()[i]->canonicalName() +
|
||||
"\"" +
|
||||
(result.message().empty() ? "." : ": " + result.message())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void TypeChecker::typeCheckBytesConcatFunction(
|
||||
FunctionCall const& _functionCall,
|
||||
FunctionType const* _functionType
|
||||
@ -2507,6 +2619,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
case FunctionType::Kind::ABIEncodePacked:
|
||||
case FunctionType::Kind::ABIEncodeWithSelector:
|
||||
case FunctionType::Kind::ABIEncodeWithSignature:
|
||||
case FunctionType::Kind::ABIEncodeCall:
|
||||
{
|
||||
typeCheckABIEncodeFunctions(_functionCall, functionType);
|
||||
returnTypes = functionType->returnParameterTypes();
|
||||
|
@ -110,6 +110,9 @@ private:
|
||||
FunctionTypePointer _functionType
|
||||
);
|
||||
|
||||
/// Performs checks specific to the ABI encode functions of type ABIEncodeCall
|
||||
void typeCheckABIEncodeCallFunction(FunctionCall const& _functionCall);
|
||||
|
||||
/// Performs general checks and checks specific to bytes concat function call
|
||||
void typeCheckBytesConcatFunction(
|
||||
FunctionCall const& _functionCall,
|
||||
|
@ -367,6 +367,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
|
||||
{MagicType::Kind::ABI, "encode"},
|
||||
{MagicType::Kind::ABI, "encodePacked"},
|
||||
{MagicType::Kind::ABI, "encodeWithSelector"},
|
||||
{MagicType::Kind::ABI, "encodeCall"},
|
||||
{MagicType::Kind::ABI, "encodeWithSignature"},
|
||||
{MagicType::Kind::Message, "data"},
|
||||
{MagicType::Kind::Message, "sig"},
|
||||
|
@ -2939,6 +2939,7 @@ string FunctionType::richIdentifier() const
|
||||
case Kind::ABIEncode: id += "abiencode"; break;
|
||||
case Kind::ABIEncodePacked: id += "abiencodepacked"; break;
|
||||
case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break;
|
||||
case Kind::ABIEncodeCall: id += "abiencodecall"; break;
|
||||
case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break;
|
||||
case Kind::ABIDecode: id += "abidecode"; break;
|
||||
case Kind::MetaType: id += "metatype"; break;
|
||||
@ -3503,6 +3504,7 @@ bool FunctionType::isPure() const
|
||||
m_kind == Kind::ABIEncode ||
|
||||
m_kind == Kind::ABIEncodePacked ||
|
||||
m_kind == Kind::ABIEncodeWithSelector ||
|
||||
m_kind == Kind::ABIEncodeCall ||
|
||||
m_kind == Kind::ABIEncodeWithSignature ||
|
||||
m_kind == Kind::ABIDecode ||
|
||||
m_kind == Kind::MetaType ||
|
||||
@ -4005,6 +4007,15 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
|
||||
true,
|
||||
StateMutability::Pure
|
||||
)},
|
||||
{"encodeCall", TypeProvider::function(
|
||||
TypePointers{},
|
||||
TypePointers{TypeProvider::array(DataLocation::Memory)},
|
||||
strings{},
|
||||
strings{1, ""},
|
||||
FunctionType::Kind::ABIEncodeCall,
|
||||
true,
|
||||
StateMutability::Pure
|
||||
)},
|
||||
{"encodeWithSignature", TypeProvider::function(
|
||||
TypePointers{TypeProvider::array(DataLocation::Memory, true)},
|
||||
TypePointers{TypeProvider::array(DataLocation::Memory)},
|
||||
|
@ -1237,6 +1237,7 @@ public:
|
||||
ABIEncode,
|
||||
ABIEncodePacked,
|
||||
ABIEncodeWithSelector,
|
||||
ABIEncodeCall,
|
||||
ABIEncodeWithSignature,
|
||||
ABIDecode,
|
||||
GasLeft, ///< gasleft()
|
||||
|
@ -1236,28 +1236,47 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
case FunctionType::Kind::ABIEncode:
|
||||
case FunctionType::Kind::ABIEncodePacked:
|
||||
case FunctionType::Kind::ABIEncodeWithSelector:
|
||||
case FunctionType::Kind::ABIEncodeCall:
|
||||
case FunctionType::Kind::ABIEncodeWithSignature:
|
||||
{
|
||||
bool const isPacked = function.kind() == FunctionType::Kind::ABIEncodePacked;
|
||||
bool const hasSelectorOrSignature =
|
||||
function.kind() == FunctionType::Kind::ABIEncodeWithSelector ||
|
||||
function.kind() == FunctionType::Kind::ABIEncodeCall ||
|
||||
function.kind() == FunctionType::Kind::ABIEncodeWithSignature;
|
||||
|
||||
TypePointers argumentTypes;
|
||||
TypePointers targetTypes;
|
||||
|
||||
ASTNode::listAccept(arguments, *this);
|
||||
|
||||
if (function.kind() == FunctionType::Kind::ABIEncodeCall)
|
||||
{
|
||||
solAssert(arguments.size() == 2);
|
||||
|
||||
auto const functionPtr = dynamic_cast<FunctionTypePointer>(arguments[0]->annotation().type);
|
||||
solAssert(functionPtr);
|
||||
solAssert(functionPtr->sizeOnStack() == 2);
|
||||
|
||||
// Account for tuples with one component which become that component
|
||||
if (auto const tupleType = dynamic_cast<TupleType const*>(arguments[1]->annotation().type))
|
||||
argumentTypes = tupleType->components();
|
||||
else
|
||||
argumentTypes.emplace_back(arguments[1]->annotation().type);
|
||||
}
|
||||
else
|
||||
for (unsigned i = 0; i < arguments.size(); ++i)
|
||||
{
|
||||
arguments[i]->accept(*this);
|
||||
// Do not keep the selector as part of the ABI encoded args
|
||||
if (!hasSelectorOrSignature || i > 0)
|
||||
argumentTypes.push_back(arguments[i]->annotation().type);
|
||||
}
|
||||
|
||||
utils().fetchFreeMemoryPointer();
|
||||
// stack now: [<selector>] <arg1> .. <argN> <free_mem>
|
||||
// stack now: [<selector/functionPointer/signature>] <arg1> .. <argN> <free_mem>
|
||||
|
||||
// adjust by 32(+4) bytes to accommodate the length(+selector)
|
||||
m_context << u256(32 + (hasSelectorOrSignature ? 4 : 0)) << Instruction::ADD;
|
||||
// stack now: [<selector>] <arg1> .. <argN> <data_encoding_area_start>
|
||||
// stack now: [<selector/functionPointer/signature>] <arg1> .. <argN> <data_encoding_area_start>
|
||||
|
||||
if (isPacked)
|
||||
{
|
||||
@ -1270,7 +1289,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
utils().abiEncode(argumentTypes, TypePointers());
|
||||
}
|
||||
utils().fetchFreeMemoryPointer();
|
||||
// stack: [<selector>] <data_encoding_area_end> <bytes_memory_ptr>
|
||||
// stack: [<selector/functionPointer/signature>] <data_encoding_area_end> <bytes_memory_ptr>
|
||||
|
||||
// size is end minus start minus length slot
|
||||
m_context.appendInlineAssembly(R"({
|
||||
@ -1278,16 +1297,17 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
})", {"mem_end", "mem_ptr"});
|
||||
m_context << Instruction::SWAP1;
|
||||
utils().storeFreeMemoryPointer();
|
||||
// stack: [<selector>] <memory ptr>
|
||||
// stack: [<selector/functionPointer/signature>] <memory ptr>
|
||||
|
||||
if (hasSelectorOrSignature)
|
||||
{
|
||||
// stack: <selector> <memory pointer>
|
||||
// stack: <selector/functionPointer/signature> <memory pointer>
|
||||
solAssert(arguments.size() >= 1, "");
|
||||
Type const* selectorType = arguments[0]->annotation().type;
|
||||
utils().moveIntoStack(selectorType->sizeOnStack());
|
||||
Type const* dataOnStack = selectorType;
|
||||
// stack: <memory pointer> <selector>
|
||||
|
||||
// stack: <memory pointer> <selector/functionPointer/signature>
|
||||
if (function.kind() == FunctionType::Kind::ABIEncodeWithSignature)
|
||||
{
|
||||
// hash the signature
|
||||
@ -1299,7 +1319,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
else
|
||||
{
|
||||
utils().fetchFreeMemoryPointer();
|
||||
// stack: <memory pointer> <selector> <free mem ptr>
|
||||
// stack: <memory pointer> <signature> <free mem ptr>
|
||||
utils().packedEncode(TypePointers{selectorType}, TypePointers());
|
||||
utils().toSizeAfterFreeMemoryPointer();
|
||||
m_context << Instruction::KECCAK256;
|
||||
@ -1308,10 +1328,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
dataOnStack = TypeProvider::fixedBytes(32);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (function.kind() == FunctionType::Kind::ABIEncodeCall)
|
||||
{
|
||||
solAssert(function.kind() == FunctionType::Kind::ABIEncodeWithSelector, "");
|
||||
// stack: <memory pointer> <functionPointer>
|
||||
// Extract selector from the stack
|
||||
m_context << Instruction::SWAP1 << Instruction::POP;
|
||||
// Conversion will be done below
|
||||
dataOnStack = TypeProvider::uint(32);
|
||||
}
|
||||
else
|
||||
solAssert(function.kind() == FunctionType::Kind::ABIEncodeWithSelector, "");
|
||||
|
||||
utils().convertType(*dataOnStack, FixedBytesType(4), true);
|
||||
|
||||
|
@ -2057,7 +2057,7 @@ string YulUtilFunctions::copyValueArrayStorageToStorageFunction(ArrayType const&
|
||||
solAssert(!_fromType.isValueType(), "");
|
||||
templ("functionName", functionName);
|
||||
templ("resizeArray", resizeArrayFunction(_toType));
|
||||
templ("arrayLength",arrayLengthFunction(_fromType));
|
||||
templ("arrayLength", arrayLengthFunction(_fromType));
|
||||
templ("panic", panicFunction(PanicCode::ResourceError));
|
||||
templ("srcDataLocation", arrayDataAreaFunction(_fromType));
|
||||
templ("dstDataLocation", arrayDataAreaFunction(_toType));
|
||||
@ -2065,7 +2065,14 @@ string YulUtilFunctions::copyValueArrayStorageToStorageFunction(ArrayType const&
|
||||
unsigned itemsPerSlot = 32 / _toType.storageStride();
|
||||
templ("itemsPerSlot", to_string(itemsPerSlot));
|
||||
templ("multipleItemsPerSlotDst", itemsPerSlot > 1);
|
||||
bool sameType = _fromType.baseType() == _toType.baseType();
|
||||
bool sameType = *_fromType.baseType() == *_toType.baseType();
|
||||
if (auto functionType = dynamic_cast<FunctionType const*>(_fromType.baseType()))
|
||||
{
|
||||
solAssert(functionType->equalExcludingStateMutability(
|
||||
dynamic_cast<FunctionType const&>(*_toType.baseType())
|
||||
));
|
||||
sameType = true;
|
||||
}
|
||||
templ("sameType", sameType);
|
||||
if (sameType)
|
||||
{
|
||||
|
@ -1104,29 +1104,57 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
case FunctionType::Kind::ABIEncode:
|
||||
case FunctionType::Kind::ABIEncodePacked:
|
||||
case FunctionType::Kind::ABIEncodeWithSelector:
|
||||
case FunctionType::Kind::ABIEncodeCall:
|
||||
case FunctionType::Kind::ABIEncodeWithSignature:
|
||||
{
|
||||
bool const isPacked = functionType->kind() == FunctionType::Kind::ABIEncodePacked;
|
||||
solAssert(functionType->padArguments() != isPacked, "");
|
||||
bool const hasSelectorOrSignature =
|
||||
functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector ||
|
||||
functionType->kind() == FunctionType::Kind::ABIEncodeCall ||
|
||||
functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature;
|
||||
|
||||
TypePointers argumentTypes;
|
||||
TypePointers targetTypes;
|
||||
vector<string> argumentVars;
|
||||
string selector;
|
||||
vector<ASTPointer<Expression const>> argumentsOfEncodeFunction;
|
||||
|
||||
if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
|
||||
{
|
||||
solAssert(arguments.size() == 2, "");
|
||||
// Account for tuples with one component which become that component
|
||||
if (type(*arguments[1]).category() == Type::Category::Tuple)
|
||||
{
|
||||
auto const& tupleExpression = dynamic_cast<TupleExpression const&>(*arguments[1]);
|
||||
for (auto component: tupleExpression.components())
|
||||
argumentsOfEncodeFunction.push_back(component);
|
||||
}
|
||||
else
|
||||
argumentsOfEncodeFunction.push_back(arguments[1]);
|
||||
}
|
||||
else
|
||||
for (size_t i = 0; i < arguments.size(); ++i)
|
||||
{
|
||||
// ignore selector
|
||||
if (hasSelectorOrSignature && i == 0)
|
||||
continue;
|
||||
argumentTypes.emplace_back(&type(*arguments[i]));
|
||||
targetTypes.emplace_back(type(*arguments[i]).fullEncodingType(false, true, isPacked));
|
||||
argumentVars += IRVariable(*arguments[i]).stackSlots();
|
||||
argumentsOfEncodeFunction.push_back(arguments[i]);
|
||||
}
|
||||
|
||||
string selector;
|
||||
if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature)
|
||||
for (auto const& argument: argumentsOfEncodeFunction)
|
||||
{
|
||||
argumentTypes.emplace_back(&type(*argument));
|
||||
targetTypes.emplace_back(type(*argument).fullEncodingType(false, true, isPacked));
|
||||
argumentVars += IRVariable(*argument).stackSlots();
|
||||
}
|
||||
|
||||
if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
|
||||
selector = convert(
|
||||
IRVariable(*arguments[0]).part("functionSelector"),
|
||||
*TypeProvider::fixedBytes(4)
|
||||
).name();
|
||||
else if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature)
|
||||
{
|
||||
// hash the signature
|
||||
Type const& selectorType = type(*arguments.front());
|
||||
@ -1833,7 +1861,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
|
||||
define(_memberAccess) << requestedValue << "\n";
|
||||
}
|
||||
else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member))
|
||||
else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeCall", "encodeWithSignature", "decode"}.count(member))
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
@ -637,6 +637,7 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
|
||||
case FunctionType::Kind::ABIEncode:
|
||||
case FunctionType::Kind::ABIEncodePacked:
|
||||
case FunctionType::Kind::ABIEncodeWithSelector:
|
||||
case FunctionType::Kind::ABIEncodeCall:
|
||||
case FunctionType::Kind::ABIEncodeWithSignature:
|
||||
visitABIFunction(_funCall);
|
||||
break;
|
||||
@ -3041,6 +3042,7 @@ set<FunctionCall const*> SMTEncoder::collectABICalls(ASTNode const* _node)
|
||||
case FunctionType::Kind::ABIEncode:
|
||||
case FunctionType::Kind::ABIEncodePacked:
|
||||
case FunctionType::Kind::ABIEncodeWithSelector:
|
||||
case FunctionType::Kind::ABIEncodeCall:
|
||||
case FunctionType::Kind::ABIEncodeWithSignature:
|
||||
case FunctionType::Kind::ABIDecode:
|
||||
abiCalls.insert(&_funCall);
|
||||
|
@ -236,6 +236,15 @@ void SymbolicState::buildABIFunctions(set<FunctionCall const*> const& _abiFuncti
|
||||
else
|
||||
solAssert(false, "Unexpected argument of abi.decode");
|
||||
}
|
||||
else if (t->kind() == FunctionType::Kind::ABIEncodeCall)
|
||||
{
|
||||
// abi.encodeCall : (functionPointer, tuple_of_args_or_one_non_tuple_arg(arguments)) -> bytes
|
||||
solAssert(args.size() == 2, "Unexpected number of arguments for abi.encodeCall");
|
||||
|
||||
outTypes.emplace_back(TypeProvider::bytesMemory());
|
||||
inTypes.emplace_back(args.at(0)->annotation().type);
|
||||
inTypes.emplace_back(args.at(1)->annotation().type);
|
||||
}
|
||||
else
|
||||
{
|
||||
outTypes = returnTypes;
|
||||
|
64
libsolidity/lsp/FileRepository.cpp
Normal file
64
libsolidity/lsp/FileRepository.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include <libsolidity/lsp/FileRepository.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::lsp;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
string stripFilePrefix(string const& _path)
|
||||
{
|
||||
if (_path.find("file://") == 0)
|
||||
return _path.substr(7);
|
||||
else
|
||||
return _path;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
string FileRepository::sourceUnitNameToClientPath(string const& _sourceUnitName) const
|
||||
{
|
||||
if (m_sourceUnitNamesToClientPaths.count(_sourceUnitName))
|
||||
return m_sourceUnitNamesToClientPaths.at(_sourceUnitName);
|
||||
else if (_sourceUnitName.find("file://") == 0)
|
||||
return _sourceUnitName;
|
||||
else
|
||||
return "file://" + (m_fileReader.basePath() / _sourceUnitName).generic_string();
|
||||
}
|
||||
|
||||
string FileRepository::clientPathToSourceUnitName(string const& _path) const
|
||||
{
|
||||
return m_fileReader.cliPathToSourceUnitName(stripFilePrefix(_path));
|
||||
}
|
||||
|
||||
map<string, string> const& FileRepository::sourceUnits() const
|
||||
{
|
||||
return m_fileReader.sourceUnits();
|
||||
}
|
||||
|
||||
void FileRepository::setSourceByClientPath(string const& _uri, string _text)
|
||||
{
|
||||
// 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));
|
||||
}
|
53
libsolidity/lsp/FileRepository.h
Normal file
53
libsolidity/lsp/FileRepository.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/interface/FileReader.h>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace solidity::lsp
|
||||
{
|
||||
|
||||
class FileRepository
|
||||
{
|
||||
public:
|
||||
explicit FileRepository(boost::filesystem::path const& _basePath):
|
||||
m_fileReader(_basePath) {}
|
||||
|
||||
boost::filesystem::path const& basePath() const { return m_fileReader.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;
|
||||
|
||||
/// @returns all sources by their compiler-internal source unit name.
|
||||
std::map<std::string, std::string> const& sourceUnits() const;
|
||||
/// Changes the source identified by the LSP client path _uri to _text.
|
||||
void setSourceByClientPath(std::string const& _uri, std::string _text);
|
||||
|
||||
frontend::ReadCallback::Callback reader() { return m_fileReader.reader(); }
|
||||
|
||||
private:
|
||||
std::map<std::string, std::string> m_sourceUnitNamesToClientPaths;
|
||||
frontend::FileReader m_fileReader;
|
||||
};
|
||||
|
||||
}
|
402
libsolidity/lsp/LanguageServer.cpp
Normal file
402
libsolidity/lsp/LanguageServer.cpp
Normal file
@ -0,0 +1,402 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/ASTUtils.h>
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
#include <libsolidity/interface/ReadFile.h>
|
||||
#include <libsolidity/interface/StandardCompiler.h>
|
||||
#include <libsolidity/lsp/LanguageServer.h>
|
||||
|
||||
#include <liblangutil/SourceReferenceExtractor.h>
|
||||
#include <liblangutil/CharStream.h>
|
||||
|
||||
#include <libsolutil/Visitor.h>
|
||||
#include <libsolutil/JSON.h>
|
||||
|
||||
#include <boost/exception/diagnostic_information.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
|
||||
using namespace solidity::lsp;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
Json::Value toJson(LineColumn _pos)
|
||||
{
|
||||
Json::Value json = Json::objectValue;
|
||||
json["line"] = max(_pos.line, 0);
|
||||
json["character"] = max(_pos.column, 0);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
Json::Value toJsonRange(LineColumn const& _start, LineColumn const& _end)
|
||||
{
|
||||
Json::Value json;
|
||||
json["start"] = toJson(_start);
|
||||
json["end"] = toJson(_end);
|
||||
return json;
|
||||
}
|
||||
|
||||
optional<LineColumn> parseLineColumn(Json::Value const& _lineColumn)
|
||||
{
|
||||
if (_lineColumn.isObject() && _lineColumn["line"].isInt() && _lineColumn["character"].isInt())
|
||||
return LineColumn{_lineColumn["line"].asInt(), _lineColumn["character"].asInt()};
|
||||
else
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
constexpr int toDiagnosticSeverity(Error::Type _errorType)
|
||||
{
|
||||
// 1=Error, 2=Warning, 3=Info, 4=Hint
|
||||
switch (Error::errorSeverity(_errorType))
|
||||
{
|
||||
case Error::Severity::Error: return 1;
|
||||
case Error::Severity::Warning: return 2;
|
||||
case Error::Severity::Info: return 3;
|
||||
}
|
||||
solAssert(false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LanguageServer::LanguageServer(Transport& _transport):
|
||||
m_client{_transport},
|
||||
m_handlers{
|
||||
{"$/cancelRequest", [](auto, auto) {/*nothing for now as we are synchronous */}},
|
||||
{"cancelRequest", [](auto, auto) {/*nothing for now as we are synchronous */}},
|
||||
{"exit", [this](auto, auto) { m_state = (m_state == State::ShutdownRequested ? State::ExitRequested : State::ExitWithoutShutdown); }},
|
||||
{"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)},
|
||||
{"initialized", [](auto, auto) {}},
|
||||
{"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }},
|
||||
{"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _1, _2)},
|
||||
{"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _1, _2)},
|
||||
{"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _1, _2)},
|
||||
{"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _1, _2)},
|
||||
},
|
||||
m_fileRepository("/" /* basePath */),
|
||||
m_compilerStack{m_fileRepository.reader()}
|
||||
{
|
||||
}
|
||||
|
||||
optional<SourceLocation> LanguageServer::parsePosition(
|
||||
string const& _sourceUnitName,
|
||||
Json::Value const& _position
|
||||
) const
|
||||
{
|
||||
if (!m_fileRepository.sourceUnits().count(_sourceUnitName))
|
||||
return nullopt;
|
||||
|
||||
if (optional<LineColumn> lineColumn = parseLineColumn(_position))
|
||||
if (optional<int> const offset = CharStream::translateLineColumnToPosition(
|
||||
m_fileRepository.sourceUnits().at(_sourceUnitName),
|
||||
*lineColumn
|
||||
))
|
||||
return SourceLocation{*offset, *offset, make_shared<string>(_sourceUnitName)};
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<SourceLocation> LanguageServer::parseRange(string const& _sourceUnitName, Json::Value const& _range) const
|
||||
{
|
||||
if (!_range.isObject())
|
||||
return nullopt;
|
||||
optional<SourceLocation> start = parsePosition(_sourceUnitName, _range["start"]);
|
||||
optional<SourceLocation> end = parsePosition(_sourceUnitName, _range["end"]);
|
||||
if (!start || !end)
|
||||
return nullopt;
|
||||
solAssert(*start->sourceName == *end->sourceName);
|
||||
start->end = end->end;
|
||||
return start;
|
||||
}
|
||||
|
||||
Json::Value LanguageServer::toRange(SourceLocation const& _location) const
|
||||
{
|
||||
if (!_location.hasText())
|
||||
return toJsonRange({}, {});
|
||||
|
||||
solAssert(_location.sourceName, "");
|
||||
CharStream const& stream = m_compilerStack.charStream(*_location.sourceName);
|
||||
LineColumn start = stream.translatePositionToLineColumn(_location.start);
|
||||
LineColumn end = stream.translatePositionToLineColumn(_location.end);
|
||||
return toJsonRange(start, end);
|
||||
}
|
||||
|
||||
Json::Value LanguageServer::toJson(SourceLocation const& _location) const
|
||||
{
|
||||
solAssert(_location.sourceName);
|
||||
Json::Value item = Json::objectValue;
|
||||
item["uri"] = m_fileRepository.sourceUnitNameToClientPath(*_location.sourceName);
|
||||
item["range"] = toRange(_location);
|
||||
return item;
|
||||
}
|
||||
|
||||
void LanguageServer::changeConfiguration(Json::Value const& _settings)
|
||||
{
|
||||
m_settingsObject = _settings;
|
||||
}
|
||||
|
||||
void LanguageServer::compile()
|
||||
{
|
||||
// For files that are not open, we have to take changes on disk into account,
|
||||
// so we just remove all non-open files.
|
||||
|
||||
FileRepository oldRepository(m_fileRepository.basePath());
|
||||
swap(oldRepository, m_fileRepository);
|
||||
|
||||
for (string const& fileName: m_openFiles)
|
||||
m_fileRepository.setSourceByClientPath(
|
||||
fileName,
|
||||
oldRepository.sourceUnits().at(oldRepository.clientPathToSourceUnitName(fileName))
|
||||
);
|
||||
|
||||
// TODO: optimize! do not recompile if nothing has changed (file(s) not flagged dirty).
|
||||
|
||||
m_compilerStack.reset(false);
|
||||
m_compilerStack.setSources(m_fileRepository.sourceUnits());
|
||||
m_compilerStack.compile(CompilerStack::State::AnalysisPerformed);
|
||||
}
|
||||
|
||||
void LanguageServer::compileAndUpdateDiagnostics()
|
||||
{
|
||||
compile();
|
||||
|
||||
// These are the source units we will sent diagnostics to the client for sure,
|
||||
// even if it is just to clear previous diagnostics.
|
||||
map<string, Json::Value> diagnosticsBySourceUnit;
|
||||
for (string const& sourceUnitName: m_fileRepository.sourceUnits() | ranges::views::keys)
|
||||
diagnosticsBySourceUnit[sourceUnitName] = Json::arrayValue;
|
||||
for (string const& sourceUnitName: m_nonemptyDiagnostics)
|
||||
diagnosticsBySourceUnit[sourceUnitName] = Json::arrayValue;
|
||||
|
||||
for (shared_ptr<Error const> const& error: m_compilerStack.errors())
|
||||
{
|
||||
SourceLocation const* location = error->sourceLocation();
|
||||
if (!location || !location->sourceName)
|
||||
// LSP only has diagnostics applied to individual files.
|
||||
continue;
|
||||
|
||||
Json::Value jsonDiag;
|
||||
jsonDiag["source"] = "solc";
|
||||
jsonDiag["severity"] = toDiagnosticSeverity(error->type());
|
||||
jsonDiag["code"] = Json::UInt64{error->errorId().error};
|
||||
string message = error->typeName() + ":";
|
||||
if (string const* comment = error->comment())
|
||||
message += " " + *comment;
|
||||
jsonDiag["message"] = move(message);
|
||||
jsonDiag["range"] = toRange(*location);
|
||||
|
||||
if (auto const* secondary = error->secondarySourceLocation())
|
||||
for (auto&& [secondaryMessage, secondaryLocation]: secondary->infos)
|
||||
{
|
||||
Json::Value jsonRelated;
|
||||
jsonRelated["message"] = secondaryMessage;
|
||||
jsonRelated["location"] = toJson(secondaryLocation);
|
||||
jsonDiag["relatedInformation"].append(jsonRelated);
|
||||
}
|
||||
|
||||
diagnosticsBySourceUnit[*location->sourceName].append(jsonDiag);
|
||||
}
|
||||
|
||||
m_nonemptyDiagnostics.clear();
|
||||
for (auto&& [sourceUnitName, diagnostics]: diagnosticsBySourceUnit)
|
||||
{
|
||||
Json::Value params;
|
||||
params["uri"] = m_fileRepository.sourceUnitNameToClientPath(sourceUnitName);
|
||||
if (!diagnostics.empty())
|
||||
m_nonemptyDiagnostics.insert(sourceUnitName);
|
||||
params["diagnostics"] = move(diagnostics);
|
||||
m_client.notify("textDocument/publishDiagnostics", move(params));
|
||||
}
|
||||
}
|
||||
|
||||
bool LanguageServer::run()
|
||||
{
|
||||
while (m_state != State::ExitRequested && m_state != State::ExitWithoutShutdown && !m_client.closed())
|
||||
{
|
||||
MessageID id;
|
||||
try
|
||||
{
|
||||
optional<Json::Value> const jsonMessage = m_client.receive();
|
||||
if (!jsonMessage)
|
||||
continue;
|
||||
|
||||
if ((*jsonMessage)["method"].isString())
|
||||
{
|
||||
string const methodName = (*jsonMessage)["method"].asString();
|
||||
id = (*jsonMessage)["id"];
|
||||
|
||||
if (auto handler = valueOrDefault(m_handlers, methodName))
|
||||
handler(id, (*jsonMessage)["params"]);
|
||||
else
|
||||
m_client.error(id, ErrorCode::MethodNotFound, "Unknown method " + methodName);
|
||||
}
|
||||
else
|
||||
m_client.error({}, ErrorCode::ParseError, "\"method\" has to be a string.");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
m_client.error(id, ErrorCode::InternalError, "Unhandled exception: "s + boost::current_exception_diagnostic_information());
|
||||
}
|
||||
}
|
||||
return m_state == State::ExitRequested;
|
||||
}
|
||||
|
||||
bool LanguageServer::checkServerInitialized(MessageID _id)
|
||||
{
|
||||
if (m_state != State::Initialized)
|
||||
{
|
||||
m_client.error(_id, ErrorCode::ServerNotInitialized, "Server is not properly initialized.");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
|
||||
{
|
||||
if (m_state != State::Started)
|
||||
{
|
||||
m_client.error(_id, ErrorCode::RequestFailed, "Initialize called at the wrong time.");
|
||||
return;
|
||||
}
|
||||
m_state = State::Initialized;
|
||||
|
||||
// The default of FileReader is to use `.`, but the path from where the LSP was started
|
||||
// should not matter.
|
||||
string rootPath("/");
|
||||
if (Json::Value uri = _args["rootUri"])
|
||||
{
|
||||
rootPath = uri.asString();
|
||||
if (!boost::starts_with(rootPath, "file://"))
|
||||
{
|
||||
m_client.error(_id, ErrorCode::InvalidParams, "rootUri only supports file URI scheme.");
|
||||
return;
|
||||
}
|
||||
rootPath = rootPath.substr(7);
|
||||
}
|
||||
else if (Json::Value rootPath = _args["rootPath"])
|
||||
rootPath = rootPath.asString();
|
||||
|
||||
m_fileRepository = FileRepository(boost::filesystem::path(rootPath));
|
||||
if (_args["initializationOptions"].isObject())
|
||||
changeConfiguration(_args["initializationOptions"]);
|
||||
|
||||
Json::Value replyArgs;
|
||||
replyArgs["serverInfo"]["name"] = "solc";
|
||||
replyArgs["serverInfo"]["version"] = string(VersionNumber);
|
||||
replyArgs["capabilities"]["textDocumentSync"]["openClose"] = true;
|
||||
replyArgs["capabilities"]["textDocumentSync"]["change"] = 2; // 0=none, 1=full, 2=incremental
|
||||
|
||||
m_client.reply(_id, move(replyArgs));
|
||||
}
|
||||
|
||||
|
||||
void LanguageServer::handleWorkspaceDidChangeConfiguration(MessageID _id, Json::Value const& _args)
|
||||
{
|
||||
if (!checkServerInitialized(_id))
|
||||
return;
|
||||
|
||||
if (_args["settings"].isObject())
|
||||
changeConfiguration(_args["settings"]);
|
||||
}
|
||||
|
||||
void LanguageServer::handleTextDocumentDidOpen(MessageID _id, Json::Value const& _args)
|
||||
{
|
||||
if (!checkServerInitialized(_id))
|
||||
return;
|
||||
|
||||
if (!_args["textDocument"])
|
||||
m_client.error(_id, ErrorCode::RequestFailed, "Text document parameter missing.");
|
||||
|
||||
string text = _args["textDocument"]["text"].asString();
|
||||
string uri = _args["textDocument"]["uri"].asString();
|
||||
m_openFiles.insert(uri);
|
||||
m_fileRepository.setSourceByClientPath(uri, move(text));
|
||||
compileAndUpdateDiagnostics();
|
||||
}
|
||||
|
||||
void LanguageServer::handleTextDocumentDidChange(MessageID _id, Json::Value const& _args)
|
||||
{
|
||||
if (!checkServerInitialized(_id))
|
||||
return;
|
||||
|
||||
string const uri = _args["textDocument"]["uri"].asString();
|
||||
|
||||
for (Json::Value jsonContentChange: _args["contentChanges"])
|
||||
{
|
||||
if (!jsonContentChange.isObject())
|
||||
{
|
||||
m_client.error(_id, ErrorCode::RequestFailed, "Invalid content reference.");
|
||||
return;
|
||||
}
|
||||
|
||||
string const sourceUnitName = m_fileRepository.clientPathToSourceUnitName(uri);
|
||||
if (!m_fileRepository.sourceUnits().count(sourceUnitName))
|
||||
{
|
||||
m_client.error(_id, ErrorCode::RequestFailed, "Unknown file: " + uri);
|
||||
return;
|
||||
}
|
||||
|
||||
string text = jsonContentChange["text"].asString();
|
||||
if (jsonContentChange["range"].isObject()) // otherwise full content update
|
||||
{
|
||||
optional<SourceLocation> change = parseRange(sourceUnitName, jsonContentChange["range"]);
|
||||
if (!change || !change->hasText())
|
||||
{
|
||||
m_client.error(
|
||||
_id,
|
||||
ErrorCode::RequestFailed,
|
||||
"Invalid source range: " + jsonCompactPrint(jsonContentChange["range"])
|
||||
);
|
||||
return;
|
||||
}
|
||||
string buffer = m_fileRepository.sourceUnits().at(sourceUnitName);
|
||||
buffer.replace(static_cast<size_t>(change->start), static_cast<size_t>(change->end - change->start), move(text));
|
||||
text = move(buffer);
|
||||
}
|
||||
m_fileRepository.setSourceByClientPath(uri, move(text));
|
||||
}
|
||||
|
||||
compileAndUpdateDiagnostics();
|
||||
}
|
||||
|
||||
void LanguageServer::handleTextDocumentDidClose(MessageID _id, Json::Value const& _args)
|
||||
{
|
||||
if (!checkServerInitialized(_id))
|
||||
return;
|
||||
|
||||
if (!_args["textDocument"])
|
||||
m_client.error(_id, ErrorCode::RequestFailed, "Text document parameter missing.");
|
||||
|
||||
string uri = _args["textDocument"]["uri"].asString();
|
||||
m_openFiles.erase(uri);
|
||||
|
||||
compileAndUpdateDiagnostics();
|
||||
}
|
110
libsolidity/lsp/LanguageServer.h
Normal file
110
libsolidity/lsp/LanguageServer.h
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/lsp/Transport.h>
|
||||
#include <libsolidity/lsp/FileRepository.h>
|
||||
#include <libsolidity/interface/CompilerStack.h>
|
||||
#include <libsolidity/interface/FileReader.h>
|
||||
|
||||
#include <json/value.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace solidity::lsp
|
||||
{
|
||||
|
||||
enum class ErrorCode;
|
||||
|
||||
/**
|
||||
* Solidity Language Server, managing one LSP client.
|
||||
* This implements a subset of LSP version 3.16 that can be found at:
|
||||
* https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/
|
||||
*/
|
||||
class LanguageServer
|
||||
{
|
||||
public:
|
||||
/// @param _transport Customizable transport layer.
|
||||
explicit LanguageServer(Transport& _transport);
|
||||
|
||||
/// Re-compiles the project and updates the diagnostics pushed to the client.
|
||||
void compileAndUpdateDiagnostics();
|
||||
|
||||
/// Loops over incoming messages via the transport layer until shutdown condition is met.
|
||||
///
|
||||
/// The standard shutdown condition is when the maximum number of consecutive failures
|
||||
/// has been exceeded.
|
||||
///
|
||||
/// @return boolean indicating normal or abnormal termination.
|
||||
bool run();
|
||||
|
||||
private:
|
||||
/// Checks if the server is initialized (to be used by messages that need it to be initialized).
|
||||
/// Reports an error and returns false if not.
|
||||
bool checkServerInitialized(MessageID _id);
|
||||
void handleInitialize(MessageID _id, Json::Value const& _args);
|
||||
void handleWorkspaceDidChangeConfiguration(MessageID _id, Json::Value const& _args);
|
||||
void handleTextDocumentDidOpen(MessageID _id, Json::Value const& _args);
|
||||
void handleTextDocumentDidChange(MessageID _id, Json::Value const& _args);
|
||||
void handleTextDocumentDidClose(MessageID _id, Json::Value const& _args);
|
||||
|
||||
/// Invoked when the server user-supplied configuration changes (initiated by the client).
|
||||
void changeConfiguration(Json::Value const&);
|
||||
|
||||
/// Compile everything until after analysis phase.
|
||||
void compile();
|
||||
|
||||
std::optional<langutil::SourceLocation> parsePosition(
|
||||
std::string const& _sourceUnitName,
|
||||
Json::Value const& _position
|
||||
) const;
|
||||
/// @returns the source location given a source unit name and an LSP Range object,
|
||||
/// or nullopt on failure.
|
||||
std::optional<langutil::SourceLocation> parseRange(
|
||||
std::string const& _sourceUnitName,
|
||||
Json::Value const& _range
|
||||
) const;
|
||||
Json::Value toRange(langutil::SourceLocation const& _location) const;
|
||||
Json::Value toJson(langutil::SourceLocation const& _location) const;
|
||||
|
||||
// LSP related member fields
|
||||
using MessageHandler = std::function<void(MessageID, Json::Value const&)>;
|
||||
|
||||
enum class State { Started, Initialized, ShutdownRequested, ExitRequested, ExitWithoutShutdown };
|
||||
State m_state = State::Started;
|
||||
|
||||
Transport& m_client;
|
||||
std::map<std::string, MessageHandler> m_handlers;
|
||||
|
||||
/// Set of files known to be open by the client.
|
||||
std::set<std::string> m_openFiles;
|
||||
/// Set of source unit names for which we sent diagnostics to the client in the last iteration.
|
||||
std::set<std::string> m_nonemptyDiagnostics;
|
||||
FileRepository m_fileRepository;
|
||||
|
||||
frontend::CompilerStack m_compilerStack;
|
||||
|
||||
/// User-supplied custom configuration settings (such as EVM version).
|
||||
Json::Value m_settingsObject;
|
||||
};
|
||||
|
||||
}
|
141
libsolidity/lsp/Transport.cpp
Normal file
141
libsolidity/lsp/Transport.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
#include <libsolidity/lsp/Transport.h>
|
||||
|
||||
#include <libsolutil/JSON.h>
|
||||
#include <libsolutil/Visitor.h>
|
||||
#include <libsolutil/CommonIO.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
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<Json::Value> IOStreamTransport::receive()
|
||||
{
|
||||
auto const headers = parseHeaders();
|
||||
if (!headers)
|
||||
{
|
||||
error({}, ErrorCode::ParseError, "Could not parse RPC headers.");
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
if (!headers->count("content-length"))
|
||||
{
|
||||
error({}, ErrorCode::ParseError, "No content-length header found.");
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
string const data = util::readBytes(m_input, stoul(headers->at("content-length")));
|
||||
|
||||
Json::Value jsonMessage;
|
||||
string jsonParsingErrors;
|
||||
solidity::util::jsonParseStrict(data, jsonMessage, &jsonParsingErrors);
|
||||
if (!jsonParsingErrors.empty() || !jsonMessage || !jsonMessage.isObject())
|
||||
{
|
||||
error({}, ErrorCode::ParseError, "Could not parse RPC JSON payload. " + jsonParsingErrors);
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
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<int>(_code);
|
||||
json["error"]["message"] = move(_message);
|
||||
send(move(json), _id);
|
||||
}
|
||||
|
||||
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<map<string, string>> IOStreamTransport::parseHeaders()
|
||||
{
|
||||
map<string, string> headers;
|
||||
|
||||
while (true)
|
||||
{
|
||||
string line;
|
||||
getline(m_input, line);
|
||||
if (boost::trim_copy(line).empty())
|
||||
break;
|
||||
|
||||
auto const delimiterPos = line.find(':');
|
||||
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)
|
||||
return nullopt;
|
||||
}
|
||||
return {move(headers)};
|
||||
}
|
101
libsolidity/lsp/Transport.h
Normal file
101
libsolidity/lsp/Transport.h
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
#pragma once
|
||||
|
||||
#include <json/value.h>
|
||||
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
namespace solidity::lsp
|
||||
{
|
||||
|
||||
using MessageID = Json::Value;
|
||||
|
||||
enum class ErrorCode
|
||||
{
|
||||
// Defined by JSON RPC
|
||||
ParseError = -32700,
|
||||
MethodNotFound = -32601,
|
||||
InvalidParams = -32602,
|
||||
InternalError = -32603,
|
||||
|
||||
// Defined by the protocol.
|
||||
ServerNotInitialized = -32002,
|
||||
RequestFailed = -32803
|
||||
};
|
||||
|
||||
/**
|
||||
* Transport layer API
|
||||
*
|
||||
* The transport layer API is abstracted to make LSP more testable as well as
|
||||
* this way it could be possible to support other transports (HTTP for example) easily.
|
||||
*/
|
||||
class Transport
|
||||
{
|
||||
public:
|
||||
virtual ~Transport() = default;
|
||||
|
||||
virtual bool closed() const noexcept = 0;
|
||||
virtual std::optional<Json::Value> 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* LSP Transport using JSON-RPC over iostreams.
|
||||
*/
|
||||
class IOStreamTransport: public Transport
|
||||
{
|
||||
public:
|
||||
/// Constructs a standard stream transport layer.
|
||||
///
|
||||
/// @param _in for example std::cin (stdin)
|
||||
/// @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<Json::Value> 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<std::map<std::string, std::string>> parseHeaders();
|
||||
|
||||
private:
|
||||
std::istream& m_input;
|
||||
std::ostream& m_output;
|
||||
};
|
||||
|
||||
}
|
@ -247,7 +247,7 @@ Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Calla
|
||||
// before the operation, i.e. if PreviousSlot{2} is at a position at which _post contains VariableSlot{"tmp"},
|
||||
// then we want the variable tmp in the slot at offset 2 in the layout before the operation.
|
||||
vector<optional<StackSlot>> idealLayout(_post.size(), nullopt);
|
||||
for (auto const& [slot, idealPosition]: ranges::zip_view(_post, layout))
|
||||
for (auto&& [slot, idealPosition]: ranges::zip_view(_post, layout))
|
||||
if (PreviousSlot* previousSlot = std::get_if<PreviousSlot>(&idealPosition))
|
||||
idealLayout.at(previousSlot->slot) = slot;
|
||||
|
||||
|
@ -83,6 +83,9 @@ done
|
||||
printTask "Testing Python scripts..."
|
||||
"$REPO_ROOT/test/pyscriptTests.py"
|
||||
|
||||
printTask "Testing LSP..."
|
||||
"$REPO_ROOT/scripts/test_solidity_lsp.py" "${SOLIDITY_BUILD_DIR}/solc/solc"
|
||||
|
||||
printTask "Running commandline tests..."
|
||||
# Only run in parallel if this is run on CI infrastructure
|
||||
if [[ -n "$CI" ]]
|
||||
|
@ -38,6 +38,8 @@
|
||||
#include <libsolidity/interface/DebugSettings.h>
|
||||
#include <libsolidity/interface/ImportRemapper.h>
|
||||
#include <libsolidity/interface/StorageLayout.h>
|
||||
#include <libsolidity/lsp/LanguageServer.h>
|
||||
#include <libsolidity/lsp/Transport.h>
|
||||
|
||||
#include <libyul/AssemblyStack.h>
|
||||
|
||||
@ -56,6 +58,7 @@
|
||||
#include <libsolutil/JSON.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
#include <range/v3/view/map.hpp>
|
||||
@ -499,7 +502,11 @@ void CommandLineInterface::readInputFiles()
|
||||
m_fileReader.setStdin(readUntilEnd(m_sin));
|
||||
}
|
||||
|
||||
if (m_fileReader.sourceUnits().empty() && !m_standardJsonInput.has_value())
|
||||
if (
|
||||
m_options.input.mode != InputMode::LanguageServer &&
|
||||
m_fileReader.sourceUnits().empty() &&
|
||||
!m_standardJsonInput.has_value()
|
||||
)
|
||||
solThrow(CommandLineValidationError, "All specified input files either do not exist or are not regular files.");
|
||||
}
|
||||
|
||||
@ -624,6 +631,9 @@ void CommandLineInterface::processInput()
|
||||
m_standardJsonInput.reset();
|
||||
break;
|
||||
}
|
||||
case InputMode::LanguageServer:
|
||||
serveLSP();
|
||||
break;
|
||||
case InputMode::Assembler:
|
||||
assemble(m_options.assembly.inputLanguage, m_options.assembly.targetMachine);
|
||||
break;
|
||||
@ -884,6 +894,13 @@ void CommandLineInterface::handleAst()
|
||||
}
|
||||
}
|
||||
|
||||
void CommandLineInterface::serveLSP()
|
||||
{
|
||||
lsp::IOStreamTransport transport;
|
||||
if (!lsp::LanguageServer{transport}.run())
|
||||
solThrow(CommandLineExecutionError, "LSP terminated abnormally.");
|
||||
}
|
||||
|
||||
void CommandLineInterface::link()
|
||||
{
|
||||
solAssert(m_options.input.mode == InputMode::Linker, "");
|
||||
|
@ -82,6 +82,7 @@ private:
|
||||
void printVersion();
|
||||
void printLicense();
|
||||
void compile();
|
||||
void serveLSP();
|
||||
void link();
|
||||
void writeLinkedFiles();
|
||||
/// @returns the ``// <identifier> -> name`` hint for library placeholders.
|
||||
|
@ -59,6 +59,7 @@ static string const g_strIPFS = "ipfs";
|
||||
static string const g_strLicense = "license";
|
||||
static string const g_strLibraries = "libraries";
|
||||
static string const g_strLink = "link";
|
||||
static string const g_strLSP = "lsp";
|
||||
static string const g_strMachine = "machine";
|
||||
static string const g_strMetadataHash = "metadata-hash";
|
||||
static string const g_strMetadataLiteral = "metadata-literal";
|
||||
@ -135,6 +136,7 @@ static map<InputMode, string> const g_inputModeName = {
|
||||
{InputMode::Assembler, "assembler"},
|
||||
{InputMode::StandardJson, "standard JSON"},
|
||||
{InputMode::Linker, "linker"},
|
||||
{InputMode::LanguageServer, "language server (LSP)"},
|
||||
};
|
||||
|
||||
void CommandLineParser::checkMutuallyExclusive(vector<string> const& _optionNames)
|
||||
@ -443,6 +445,7 @@ void CommandLineParser::parseOutputSelection()
|
||||
case InputMode::Help:
|
||||
case InputMode::License:
|
||||
case InputMode::Version:
|
||||
case InputMode::LanguageServer:
|
||||
solAssert(false);
|
||||
case InputMode::Compiler:
|
||||
case InputMode::CompilerWithASTImport:
|
||||
@ -610,6 +613,11 @@ General Information)").c_str(),
|
||||
"Supported Inputs is the output of the --" + g_strStandardJSON + " or the one produced by "
|
||||
"--" + g_strCombinedJson + " " + CombinedJsonRequests::componentName(&CombinedJsonRequests::ast)).c_str()
|
||||
)
|
||||
(
|
||||
g_strLSP.c_str(),
|
||||
"Switch to language server mode (\"LSP\"). Allows the compiler to be used as an analysis backend "
|
||||
"for your favourite IDE."
|
||||
)
|
||||
;
|
||||
desc.add(alternativeInputModes);
|
||||
|
||||
@ -842,6 +850,7 @@ void CommandLineParser::processArgs()
|
||||
g_strStrictAssembly,
|
||||
g_strYul,
|
||||
g_strImportAst,
|
||||
g_strLSP
|
||||
});
|
||||
|
||||
if (m_args.count(g_strHelp) > 0)
|
||||
@ -852,6 +861,8 @@ void CommandLineParser::processArgs()
|
||||
m_options.input.mode = InputMode::Version;
|
||||
else if (m_args.count(g_strStandardJSON) > 0)
|
||||
m_options.input.mode = InputMode::StandardJson;
|
||||
else if (m_args.count(g_strLSP))
|
||||
m_options.input.mode = InputMode::LanguageServer;
|
||||
else if (m_args.count(g_strAssemble) > 0 || m_args.count(g_strStrictAssembly) > 0 || m_args.count(g_strYul) > 0)
|
||||
m_options.input.mode = InputMode::Assembler;
|
||||
else if (m_args.count(g_strLink) > 0)
|
||||
@ -887,6 +898,9 @@ void CommandLineParser::processArgs()
|
||||
joinOptionNames(invalidOptionsForCurrentInputMode)
|
||||
);
|
||||
|
||||
if (m_options.input.mode == InputMode::LanguageServer)
|
||||
return;
|
||||
|
||||
checkMutuallyExclusive({g_strColor, g_strNoColor});
|
||||
|
||||
array<string, 9> const conflictingWithStopAfter{
|
||||
|
@ -56,6 +56,7 @@ enum class InputMode
|
||||
StandardJson,
|
||||
Linker,
|
||||
Assembler,
|
||||
LanguageServer
|
||||
};
|
||||
|
||||
struct CompilerOutputs
|
||||
|
@ -1551,26 +1551,6 @@ BOOST_AUTO_TEST_CASE(generic_staticcall)
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(library_call_in_homestead)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library Lib { function m() public returns (address) { return msg.sender; } }
|
||||
contract Test {
|
||||
address public sender;
|
||||
function f() public {
|
||||
sender = Lib.m();
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "Lib");
|
||||
compileAndRun(sourceCode, 0, "Test", bytes(), map<string, h160>{{":Lib", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f()"), encodeArgs());
|
||||
ABI_CHECK(callContractFunction("sender()"), encodeArgs(m_sender));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(library_call_protection)
|
||||
{
|
||||
// This tests code that reverts a call if it is a direct call to a library
|
||||
@ -1626,38 +1606,6 @@ BOOST_AUTO_TEST_CASE(bytes_from_calldata_to_memory)
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(call_forward_bytes)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract receiver {
|
||||
uint public received;
|
||||
function recv(uint x) public { received += x + 1; }
|
||||
fallback() external { received = 0x80; }
|
||||
}
|
||||
contract sender {
|
||||
constructor() { rec = new receiver(); }
|
||||
fallback() external { savedData = msg.data; }
|
||||
function forward() public returns (bool) { address(rec).call(savedData); return true; }
|
||||
function clear() public returns (bool) { delete savedData; return true; }
|
||||
function val() public returns (uint) { return rec.received(); }
|
||||
receiver rec;
|
||||
bytes savedData;
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN();
|
||||
compileAndRun(sourceCode, 0, "sender");
|
||||
ABI_CHECK(callContractFunction("recv(uint256)", 7), bytes());
|
||||
ABI_CHECK(callContractFunction("val()"), encodeArgs(0));
|
||||
ABI_CHECK(callContractFunction("forward()"), encodeArgs(true));
|
||||
ABI_CHECK(callContractFunction("val()"), encodeArgs(8));
|
||||
ABI_CHECK(callContractFunction("clear()"), encodeArgs(true));
|
||||
ABI_CHECK(callContractFunction("val()"), encodeArgs(8));
|
||||
ABI_CHECK(callContractFunction("forward()"), encodeArgs(true));
|
||||
ABI_CHECK(callContractFunction("val()"), encodeArgs(0x80));
|
||||
);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(call_forward_bytes_length)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
@ -2576,254 +2524,6 @@ BOOST_AUTO_TEST_CASE(library_function_external)
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(library_stray_values)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library Lib { function m(uint x, uint y) public returns (uint) { return x * y; } }
|
||||
contract Test {
|
||||
function f(uint x) public returns (uint) {
|
||||
Lib;
|
||||
Lib.m;
|
||||
return x + 9;
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "Lib");
|
||||
compileAndRun(sourceCode, 0, "Test", bytes(), map<string, h160>{{":Lib", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f(uint256)", u256(33)), encodeArgs(u256(42)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(internal_types_in_library)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library Lib {
|
||||
function find(uint16[] storage _haystack, uint16 _needle) public view returns (uint)
|
||||
{
|
||||
for (uint i = 0; i < _haystack.length; ++i)
|
||||
if (_haystack[i] == _needle)
|
||||
return i;
|
||||
return type(uint).max;
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(string => uint16[]) data;
|
||||
function f() public returns (uint a, uint b)
|
||||
{
|
||||
while (data["abc"].length < 20)
|
||||
data["abc"].push();
|
||||
data["abc"][4] = 9;
|
||||
data["abc"][17] = 3;
|
||||
a = Lib.find(data["abc"], 9);
|
||||
b = Lib.find(data["abc"], 3);
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "Lib");
|
||||
compileAndRun(sourceCode, 0, "Test", bytes(), map<string, h160>{{":Lib", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(4), u256(17)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(mapping_arguments_in_library)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library Lib {
|
||||
function set(mapping(uint => uint) storage m, uint key, uint value) internal
|
||||
{
|
||||
m[key] = value;
|
||||
}
|
||||
function get(mapping(uint => uint) storage m, uint key) internal view returns (uint)
|
||||
{
|
||||
return m[key];
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(uint => uint) m;
|
||||
function set(uint256 key, uint256 value) public returns (uint)
|
||||
{
|
||||
uint oldValue = Lib.get(m, key);
|
||||
Lib.set(m, key, value);
|
||||
return oldValue;
|
||||
}
|
||||
function get(uint256 key) public view returns (uint) {
|
||||
return Lib.get(m, key);
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "Lib");
|
||||
compileAndRun(sourceCode, 0, "Test", bytes(), map<string, h160>{{":Lib", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("set(uint256,uint256)", u256(1), u256(42)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("set(uint256,uint256)", u256(2), u256(84)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("set(uint256,uint256)", u256(21), u256(7)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get(uint256)", u256(0)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get(uint256)", u256(1)), encodeArgs(u256(42)));
|
||||
ABI_CHECK(callContractFunction("get(uint256)", u256(2)), encodeArgs(u256(84)));
|
||||
ABI_CHECK(callContractFunction("get(uint256)", u256(21)), encodeArgs(u256(7)));
|
||||
ABI_CHECK(callContractFunction("set(uint256,uint256)", u256(1), u256(21)), encodeArgs(u256(42)));
|
||||
ABI_CHECK(callContractFunction("set(uint256,uint256)", u256(2), u256(42)), encodeArgs(u256(84)));
|
||||
ABI_CHECK(callContractFunction("set(uint256,uint256)", u256(21), u256(14)), encodeArgs(u256(7)));
|
||||
ABI_CHECK(callContractFunction("get(uint256)", u256(0)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get(uint256)", u256(1)), encodeArgs(u256(21)));
|
||||
ABI_CHECK(callContractFunction("get(uint256)", u256(2)), encodeArgs(u256(42)));
|
||||
ABI_CHECK(callContractFunction("get(uint256)", u256(21)), encodeArgs(u256(14)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(mapping_returns_in_library)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library Lib {
|
||||
function choose_mapping(mapping(uint => uint) storage a, mapping(uint => uint) storage b, bool c) internal pure returns(mapping(uint=>uint) storage)
|
||||
{
|
||||
return c ? a : b;
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(uint => uint) a;
|
||||
mapping(uint => uint) b;
|
||||
function set(bool choice, uint256 key, uint256 value) public returns (uint)
|
||||
{
|
||||
mapping(uint => uint) storage m = Lib.choose_mapping(a, b, choice);
|
||||
uint oldValue = m[key];
|
||||
m[key] = value;
|
||||
return oldValue;
|
||||
}
|
||||
function get(bool choice, uint256 key) public view returns (uint) {
|
||||
return Lib.choose_mapping(a, b, choice)[key];
|
||||
}
|
||||
function get_a(uint256 key) public view returns (uint) {
|
||||
return a[key];
|
||||
}
|
||||
function get_b(uint256 key) public view returns (uint) {
|
||||
return b[key];
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "Lib");
|
||||
compileAndRun(sourceCode, 0, "Test", bytes(), map<string, h160>{{":Lib", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", true, u256(1), u256(42)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", true, u256(2), u256(84)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", true, u256(21), u256(7)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", false, u256(1), u256(10)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", false, u256(2), u256(11)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", false, u256(21), u256(12)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", true, u256(0)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", true, u256(1)), encodeArgs(u256(42)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", true, u256(2)), encodeArgs(u256(84)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", true, u256(21)), encodeArgs(u256(7)));
|
||||
ABI_CHECK(callContractFunction("get_a(uint256)", u256(0)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get_a(uint256)", u256(1)), encodeArgs(u256(42)));
|
||||
ABI_CHECK(callContractFunction("get_a(uint256)", u256(2)), encodeArgs(u256(84)));
|
||||
ABI_CHECK(callContractFunction("get_a(uint256)", u256(21)), encodeArgs(u256(7)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", false, u256(0)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", false, u256(1)), encodeArgs(u256(10)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", false, u256(2)), encodeArgs(u256(11)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", false, u256(21)), encodeArgs(u256(12)));
|
||||
ABI_CHECK(callContractFunction("get_b(uint256)", u256(0)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get_b(uint256)", u256(1)), encodeArgs(u256(10)));
|
||||
ABI_CHECK(callContractFunction("get_b(uint256)", u256(2)), encodeArgs(u256(11)));
|
||||
ABI_CHECK(callContractFunction("get_b(uint256)", u256(21)), encodeArgs(u256(12)));
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", true, u256(1), u256(21)), encodeArgs(u256(42)));
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", true, u256(2), u256(42)), encodeArgs(u256(84)));
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", true, u256(21), u256(14)), encodeArgs(u256(7)));
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", false, u256(1), u256(30)), encodeArgs(u256(10)));
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", false, u256(2), u256(31)), encodeArgs(u256(11)));
|
||||
ABI_CHECK(callContractFunction("set(bool,uint256,uint256)", false, u256(21), u256(32)), encodeArgs(u256(12)));
|
||||
ABI_CHECK(callContractFunction("get_a(uint256)", u256(0)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get_a(uint256)", u256(1)), encodeArgs(u256(21)));
|
||||
ABI_CHECK(callContractFunction("get_a(uint256)", u256(2)), encodeArgs(u256(42)));
|
||||
ABI_CHECK(callContractFunction("get_a(uint256)", u256(21)), encodeArgs(u256(14)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", true, u256(0)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", true, u256(1)), encodeArgs(u256(21)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", true, u256(2)), encodeArgs(u256(42)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", true, u256(21)), encodeArgs(u256(14)));
|
||||
ABI_CHECK(callContractFunction("get_b(uint256)", u256(0)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get_b(uint256)", u256(1)), encodeArgs(u256(30)));
|
||||
ABI_CHECK(callContractFunction("get_b(uint256)", u256(2)), encodeArgs(u256(31)));
|
||||
ABI_CHECK(callContractFunction("get_b(uint256)", u256(21)), encodeArgs(u256(32)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", false, u256(0)), encodeArgs(u256(0)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", false, u256(1)), encodeArgs(u256(30)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", false, u256(2)), encodeArgs(u256(31)));
|
||||
ABI_CHECK(callContractFunction("get(bool,uint256)", false, u256(21)), encodeArgs(u256(32)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(mapping_returns_in_library_named)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library Lib {
|
||||
function f(mapping(uint => uint) storage a, mapping(uint => uint) storage b) internal returns(mapping(uint=>uint) storage r)
|
||||
{
|
||||
r = a;
|
||||
r[1] = 42;
|
||||
r = b;
|
||||
r[1] = 21;
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(uint => uint) a;
|
||||
mapping(uint => uint) b;
|
||||
function f() public returns (uint, uint, uint, uint, uint, uint)
|
||||
{
|
||||
Lib.f(a, b)[2] = 84;
|
||||
return (a[0], a[1], a[2], b[0], b[1], b[2]);
|
||||
}
|
||||
function g() public returns (uint, uint, uint, uint, uint, uint)
|
||||
{
|
||||
mapping(uint => uint) storage m = Lib.f(a, b);
|
||||
m[2] = 17;
|
||||
return (a[0], a[1], a[2], b[0], b[1], b[2]);
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "Lib");
|
||||
compileAndRun(sourceCode, 0, "Test", bytes(), map<string, h160>{{":Lib", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(0), u256(42), u256(0), u256(0), u256(21), u256(84)));
|
||||
ABI_CHECK(callContractFunction("g()"), encodeArgs(u256(0), u256(42), u256(0), u256(0), u256(21), u256(17)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(using_library_mappings_public)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library Lib {
|
||||
function set(mapping(uint => uint) storage m, uint key, uint value) public
|
||||
{
|
||||
m[key] = value;
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(uint => uint) m1;
|
||||
mapping(uint => uint) m2;
|
||||
function f() public returns (uint, uint, uint, uint, uint, uint)
|
||||
{
|
||||
Lib.set(m1, 0, 1);
|
||||
Lib.set(m1, 2, 42);
|
||||
Lib.set(m2, 0, 23);
|
||||
Lib.set(m2, 2, 99);
|
||||
return (m1[0], m1[1], m1[2], m2[0], m2[1], m2[2]);
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "Lib");
|
||||
compileAndRun(sourceCode, 0, "Test", bytes(), map<string, h160>{{":Lib", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(0), u256(42), u256(23), u256(0), u256(99)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(using_library_mappings_external)
|
||||
{
|
||||
char const* libSourceCode = R"(
|
||||
@ -2860,65 +2560,6 @@ BOOST_AUTO_TEST_CASE(using_library_mappings_external)
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(using_library_mappings_return)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library Lib {
|
||||
function choose(mapping(uint => mapping(uint => uint)) storage m, uint key) external returns (mapping(uint => uint) storage) {
|
||||
return m[key];
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(uint => mapping(uint => uint)) m;
|
||||
function f() public returns (uint, uint, uint, uint, uint, uint)
|
||||
{
|
||||
Lib.choose(m, 0)[0] = 1;
|
||||
Lib.choose(m, 0)[2] = 42;
|
||||
Lib.choose(m, 1)[0] = 23;
|
||||
Lib.choose(m, 1)[2] = 99;
|
||||
return (m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2]);
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "Lib");
|
||||
compileAndRun(sourceCode, 0, "Test", bytes(), map<string, h160>{{":Lib", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(0), u256(42), u256(23), u256(0), u256(99)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(using_library_structs)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library Lib {
|
||||
struct Data { uint a; uint[] b; }
|
||||
function set(Data storage _s) public
|
||||
{
|
||||
_s.a = 7;
|
||||
while (_s.b.length < 20)
|
||||
_s.b.push();
|
||||
_s.b[19] = 8;
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(string => Lib.Data) data;
|
||||
function f() public returns (uint a, uint b)
|
||||
{
|
||||
Lib.set(data["abc"]);
|
||||
a = data["abc"].a;
|
||||
b = data["abc"].b[19];
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "Lib");
|
||||
compileAndRun(sourceCode, 0, "Test", bytes(), map<string, h160>{{":Lib", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(7), u256(8)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(short_strings)
|
||||
{
|
||||
// This test verifies that the byte array encoding that combines length and data works
|
||||
@ -3114,146 +2755,6 @@ BOOST_AUTO_TEST_CASE(create_memory_array_allocation_size)
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(using_for_function_on_struct)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library D { struct s { uint a; } function mul(s storage self, uint x) public returns (uint) { return self.a *= x; } }
|
||||
contract C {
|
||||
using D for D.s;
|
||||
D.s public x;
|
||||
function f(uint a) public returns (uint) {
|
||||
x.a = 3;
|
||||
return x.mul(a);
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "D");
|
||||
compileAndRun(sourceCode, 0, "C", bytes(), map<string, h160>{{":D", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f(uint256)", u256(7)), encodeArgs(u256(3 * 7)));
|
||||
ABI_CHECK(callContractFunction("x()"), encodeArgs(u256(3 * 7)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(using_for_overload)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library D {
|
||||
struct s { uint a; }
|
||||
function mul(s storage self, uint x) public returns (uint) { return self.a *= x; }
|
||||
function mul(s storage self, bytes32 x) public returns (bytes32) { }
|
||||
}
|
||||
contract C {
|
||||
using D for D.s;
|
||||
D.s public x;
|
||||
function f(uint a) public returns (uint) {
|
||||
x.a = 6;
|
||||
return x.mul(a);
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "D");
|
||||
compileAndRun(sourceCode, 0, "C", bytes(), map<string, h160>{{":D", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f(uint256)", u256(7)), encodeArgs(u256(6 * 7)));
|
||||
ABI_CHECK(callContractFunction("x()"), encodeArgs(u256(6 * 7)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(using_for_by_name)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library D { struct s { uint a; } function mul(s storage self, uint x) public returns (uint) { return self.a *= x; } }
|
||||
contract C {
|
||||
using D for D.s;
|
||||
D.s public x;
|
||||
function f(uint a) public returns (uint) {
|
||||
x.a = 6;
|
||||
return x.mul({x: a});
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "D");
|
||||
compileAndRun(sourceCode, 0, "C", bytes(), map<string, h160>{{":D", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f(uint256)", u256(7)), encodeArgs(u256(6 * 7)));
|
||||
ABI_CHECK(callContractFunction("x()"), encodeArgs(u256(6 * 7)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(bound_function_in_function)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library L {
|
||||
function g(function() internal returns (uint) _t) internal returns (uint) { return _t(); }
|
||||
}
|
||||
contract C {
|
||||
using L for *;
|
||||
function f() public returns (uint) {
|
||||
return t.g();
|
||||
}
|
||||
function t() public pure returns (uint) { return 7; }
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "L");
|
||||
compileAndRun(sourceCode, 0, "C", bytes(), map<string, h160>{{":L", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(7)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(bound_function_in_var)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library D { struct s { uint a; } function mul(s storage self, uint x) public returns (uint) { return self.a *= x; } }
|
||||
contract C {
|
||||
using D for D.s;
|
||||
D.s public x;
|
||||
function f(uint a) public returns (uint) {
|
||||
x.a = 6;
|
||||
return (x.mul)({x: a});
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "D");
|
||||
compileAndRun(sourceCode, 0, "C", bytes(), map<string, h160>{{":D", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f(uint256)", u256(7)), encodeArgs(u256(6 * 7)));
|
||||
ABI_CHECK(callContractFunction("x()"), encodeArgs(u256(6 * 7)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(bound_function_to_string)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library D { function length(string memory self) public returns (uint) { return bytes(self).length; } }
|
||||
contract C {
|
||||
using D for string;
|
||||
string x;
|
||||
function f() public returns (uint) {
|
||||
x = "abc";
|
||||
return x.length();
|
||||
}
|
||||
function g() public returns (uint) {
|
||||
string memory s = "abc";
|
||||
return s.length();
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "D");
|
||||
compileAndRun(sourceCode, 0, "C", bytes(), map<string, h160>{{":D", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(3)));
|
||||
ABI_CHECK(callContractFunction("g()"), encodeArgs(u256(3)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(inline_long_string_return)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
@ -3406,26 +2907,6 @@ BOOST_AUTO_TEST_CASE(payable_function)
|
||||
BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 27 + 27);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(payable_function_calls_library)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
library L {
|
||||
function f() public returns (uint) { return 7; }
|
||||
}
|
||||
contract C {
|
||||
function f() public payable returns (uint) {
|
||||
return L.f();
|
||||
}
|
||||
}
|
||||
)";
|
||||
ALSO_VIA_YUL(
|
||||
DISABLE_EWASM_TESTRUN()
|
||||
compileAndRun(sourceCode, 0, "L");
|
||||
compileAndRun(sourceCode, 0, "C", bytes(), map<string, h160>{{":L", m_contractAddress}});
|
||||
ABI_CHECK(callContractFunctionWithValue("f()", 27), encodeArgs(u256(7)));
|
||||
)
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(non_payable_throw)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
|
6
test/libsolidity/lsp/didChange_template.sol
Normal file
6
test/libsolidity/lsp/didChange_template.sol
Normal file
@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
contract C
|
||||
{
|
||||
}
|
12
test/libsolidity/lsp/didOpen_with_import.sol
Normal file
12
test/libsolidity/lsp/didOpen_with_import.sol
Normal file
@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
import './lib.sol';
|
||||
|
||||
contract C
|
||||
{
|
||||
function f(uint a, uint b) public pure returns (uint)
|
||||
{
|
||||
return Lib.add(2 * a, b);
|
||||
}
|
||||
}
|
15
test/libsolidity/lsp/lib.sol
Normal file
15
test/libsolidity/lsp/lib.sol
Normal file
@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
library Lib
|
||||
{
|
||||
function add(uint a, uint b) public pure returns (uint result)
|
||||
{
|
||||
result = a + b;
|
||||
}
|
||||
|
||||
function warningWithUnused() public pure
|
||||
{
|
||||
uint unused;
|
||||
}
|
||||
}
|
18
test/libsolidity/lsp/publish_diagnostics_1.sol
Normal file
18
test/libsolidity/lsp/publish_diagnostics_1.sol
Normal file
@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
contract MyContract
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
uint unused; // [Warning 2072] Unused local variable.
|
||||
}
|
||||
}
|
||||
|
||||
contract D
|
||||
{
|
||||
function main() public payable returns (uint)
|
||||
{
|
||||
MyContract c = new MyContract();
|
||||
}
|
||||
}
|
21
test/libsolidity/lsp/publish_diagnostics_2.sol
Normal file
21
test/libsolidity/lsp/publish_diagnostics_2.sol
Normal file
@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
contract C
|
||||
{
|
||||
function makeSomeError() public pure returns (uint res)
|
||||
{
|
||||
uint x = "hi";
|
||||
return;
|
||||
res = 2;
|
||||
}
|
||||
}
|
||||
|
||||
contract D
|
||||
{
|
||||
function main() public payable returns (uint)
|
||||
{
|
||||
C c = new C();
|
||||
return c.makeSomeError(2, 3);
|
||||
}
|
||||
}
|
10
test/libsolidity/lsp/publish_diagnostics_3.sol
Normal file
10
test/libsolidity/lsp/publish_diagnostics_3.sol
Normal file
@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
abstract contract A {
|
||||
function a() public virtual;
|
||||
}
|
||||
|
||||
contract B is A
|
||||
{
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
pragma abicoder v2;
|
||||
contract C {
|
||||
type UnsignedNumber is uint256;
|
||||
enum Enum { First, Second, Third }
|
||||
|
||||
struct Struct {
|
||||
UnsignedNumber[] dynamicArray;
|
||||
uint256 justAnInt;
|
||||
string name;
|
||||
bytes someBytes;
|
||||
Enum theEnum;
|
||||
}
|
||||
|
||||
function callMeMaybe(Struct calldata _data, int256 _intVal, string memory _nameVal) external pure {
|
||||
assert(_data.dynamicArray.length == 3);
|
||||
assert(UnsignedNumber.unwrap(_data.dynamicArray[0]) == 0);
|
||||
assert(UnsignedNumber.unwrap(_data.dynamicArray[1]) == 1);
|
||||
assert(UnsignedNumber.unwrap(_data.dynamicArray[2]) == 2);
|
||||
assert(_data.justAnInt == 6);
|
||||
assert(keccak256(bytes(_data.name)) == keccak256("StructName"));
|
||||
assert(keccak256(_data.someBytes) == keccak256(bytes("1234")));
|
||||
assert(_data.theEnum == Enum.Second);
|
||||
assert(_intVal == 5);
|
||||
assert(keccak256(bytes(_nameVal)) == keccak256("TestName"));
|
||||
}
|
||||
|
||||
function callExternal() public returns (bool) {
|
||||
Struct memory structToSend;
|
||||
structToSend.dynamicArray = new UnsignedNumber[](3);
|
||||
structToSend.dynamicArray[0] = UnsignedNumber.wrap(0);
|
||||
structToSend.dynamicArray[1] = UnsignedNumber.wrap(1);
|
||||
structToSend.dynamicArray[2] = UnsignedNumber.wrap(2);
|
||||
structToSend.justAnInt = 6;
|
||||
structToSend.name = "StructName";
|
||||
structToSend.someBytes = bytes("1234");
|
||||
structToSend.theEnum = Enum.Second;
|
||||
|
||||
(bool success,) = address(this).call(abi.encodeCall(this.callMeMaybe, (
|
||||
structToSend,
|
||||
5,
|
||||
"TestName"
|
||||
)));
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// callExternal() -> true
|
@ -0,0 +1,63 @@
|
||||
pragma abicoder v2;
|
||||
|
||||
contract C {
|
||||
bool sideEffectRan = false;
|
||||
|
||||
function(uint256, string memory) external fPointer;
|
||||
function fExternal(uint256 p, string memory t) external {}
|
||||
string xstor;
|
||||
function getExternalFunctionPointer() public returns (function(uint256, string memory) external) {
|
||||
sideEffectRan = true;
|
||||
return this.fExternal;
|
||||
}
|
||||
|
||||
function fSignatureFromLiteral() public pure returns (bytes memory) {
|
||||
return abi.encodeWithSignature("fExternal(uint256,string)", 1, "123");
|
||||
}
|
||||
function fSignatureFromLiteralCall() public view returns (bytes memory) {
|
||||
return abi.encodeCall(this.fExternal, (1, "123"));
|
||||
}
|
||||
function fSignatureFromMemory() public pure returns (bytes memory) {
|
||||
string memory x = "fExternal(uint256,string)";
|
||||
return abi.encodeWithSignature(x, 1, "123");
|
||||
}
|
||||
function fSignatureFromMemoryCall() public view returns (bytes memory) {
|
||||
return abi.encodeCall(this.fExternal, (1,"123"));
|
||||
}
|
||||
function fSignatureFromMemorys() public returns (bytes memory) {
|
||||
xstor = "fExternal(uint256,string)";
|
||||
return abi.encodeWithSignature(xstor, 1, "123");
|
||||
}
|
||||
function fPointerCall() public returns(bytes memory) {
|
||||
fPointer = this.fExternal;
|
||||
return abi.encodeCall(fPointer, (1, "123"));
|
||||
}
|
||||
function fLocalPointerCall() public returns(bytes memory) {
|
||||
function(uint256, string memory) external localFunctionPointer = this.fExternal;
|
||||
return abi.encodeCall(localFunctionPointer, (1, "123"));
|
||||
}
|
||||
function fReturnedFunctionPointer() public returns (bytes memory) {
|
||||
return abi.encodeCall(getExternalFunctionPointer(), (1, "123"));
|
||||
}
|
||||
|
||||
function assertConsistentSelectors() public {
|
||||
assert(keccak256(fSignatureFromLiteral()) == keccak256(fSignatureFromLiteralCall()));
|
||||
assert(keccak256(fSignatureFromMemory()) == keccak256(fSignatureFromMemoryCall()));
|
||||
assert(keccak256(fSignatureFromMemoryCall()) == keccak256(fSignatureFromMemorys()));
|
||||
assert(keccak256(fPointerCall()) == keccak256(fSignatureFromLiteral()));
|
||||
assert(keccak256(fLocalPointerCall()) == keccak256(fSignatureFromLiteral()));
|
||||
assert(keccak256(fReturnedFunctionPointer()) == keccak256(fSignatureFromLiteral()));
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// assertConsistentSelectors() ->
|
||||
// fSignatureFromLiteral() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
|
||||
// fSignatureFromLiteralCall() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
|
||||
// fSignatureFromMemory() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
|
||||
// fSignatureFromMemoryCall() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
|
||||
// fSignatureFromMemorys() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
|
||||
// fPointerCall() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
|
||||
// fLocalPointerCall() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
|
||||
// fReturnedFunctionPointer() -> 0x20, 0x84, 23450202028776381066253055403048136312616272755117076566855971503345107992576, 26959946667150639794667015087019630673637144422540572481103610249216, 1725436586697640946858688965569256363112777243042596638790631055949824, 86060793054017993816230018372407419485142305772921726565498526629888, 0
|
@ -0,0 +1,28 @@
|
||||
pragma abicoder v2;
|
||||
|
||||
contract D {
|
||||
function something() external pure {}
|
||||
}
|
||||
|
||||
contract C {
|
||||
function something() external pure {}
|
||||
function test() external returns (bytes4) {
|
||||
function() external[2] memory x;
|
||||
x[0] = this.something;
|
||||
x[1] = (new D()).something;
|
||||
function() external f = x[1];
|
||||
bytes memory a = abi.encodeCall(x[0], ());
|
||||
bytes memory b = abi.encodeCall(x[1], ());
|
||||
bytes memory c = abi.encodeCall(f, ());
|
||||
assert(a.length == 4 && b.length == 4 && c.length == 4);
|
||||
assert(bytes4(a) == bytes4(b));
|
||||
assert(bytes4(a) == bytes4(c));
|
||||
assert(bytes4(a) == f.selector);
|
||||
return bytes4(a);
|
||||
}
|
||||
}
|
||||
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// test() -> 0xa7a0d53700000000000000000000000000000000000000000000000000000000
|
@ -0,0 +1,48 @@
|
||||
pragma abicoder v2;
|
||||
|
||||
contract C {
|
||||
bool sideEffectRan = false;
|
||||
|
||||
function fNoArgs() external {}
|
||||
function fArray(uint[] memory x) external {}
|
||||
function fUint(uint x, uint y) external returns (uint a, uint b) {}
|
||||
|
||||
function fSignatureFromLiteralNoArgs() public pure returns (bytes memory) {
|
||||
return abi.encodeWithSignature("fNoArgs()");
|
||||
}
|
||||
function fPointerNoArgs() public view returns (bytes memory) {
|
||||
return abi.encodeCall(this.fNoArgs, ());
|
||||
}
|
||||
|
||||
function fSignatureFromLiteralArray() public pure returns (bytes memory) {
|
||||
uint[] memory x;
|
||||
return abi.encodeWithSignature("fArray(uint256[])", x);
|
||||
}
|
||||
function fPointerArray() public view returns (bytes memory) {
|
||||
uint[] memory x;
|
||||
return abi.encodeCall(this.fArray, x);
|
||||
}
|
||||
|
||||
function fSignatureFromLiteralUint() public pure returns (bytes memory) {
|
||||
return abi.encodeWithSignature("fUint(uint256,uint256)", 12, 13);
|
||||
}
|
||||
function fPointerUint() public view returns (bytes memory) {
|
||||
return abi.encodeCall(this.fUint, (12,13));
|
||||
}
|
||||
|
||||
function assertConsistentSelectors() public view {
|
||||
assert(keccak256(fSignatureFromLiteralNoArgs()) == keccak256(fPointerNoArgs()));
|
||||
assert(keccak256(fSignatureFromLiteralArray()) == keccak256(fPointerArray()));
|
||||
assert(keccak256(fSignatureFromLiteralUint()) == keccak256(fPointerUint()));
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// assertConsistentSelectors() ->
|
||||
// fSignatureFromLiteralNoArgs() -> 0x20, 0x04, 12200448252684243758085936796735499259670113115893304444050964496075123064832
|
||||
// fPointerNoArgs() -> 0x20, 4, 12200448252684243758085936796735499259670113115893304444050964496075123064832
|
||||
// fSignatureFromLiteralArray() -> 0x20, 0x44, 4612216551196396486909126966576324289294165774260092952932219511233230929920, 862718293348820473429344482784628181556388621521298319395315527974912, 0
|
||||
// fPointerArray() -> 0x20, 0x44, 4612216551196396486909126966576324289294165774260092952932219511233230929920, 862718293348820473429344482784628181556388621521298319395315527974912, 0
|
||||
// fPointerUint() -> 0x20, 0x44, 30372892641494467502622535050667754357470287521126424526399600764424271429632, 323519360005807677536004181044235568083645733070486869773243322990592, 350479306672958317330671196131255198757282877493027442254346933239808
|
||||
// fSignatureFromLiteralUint() -> 0x20, 0x44, 30372892641494467502622535050667754357470287521126424526399600764424271429632, 323519360005807677536004181044235568083645733070486869773243322990592, 350479306672958317330671196131255198757282877493027442254346933239808
|
@ -26,7 +26,6 @@ contract C {
|
||||
}
|
||||
struct S { uint a; string b; uint16 c; }
|
||||
function f4() public pure returns (bytes memory) {
|
||||
bytes4 x = 0x12345678;
|
||||
S memory s;
|
||||
s.a = 0x1234567;
|
||||
s.b = "Lorem ipsum dolor sit ethereum........";
|
||||
|
@ -0,0 +1,53 @@
|
||||
contract C {
|
||||
function testFunction1() public {}
|
||||
function testFunction2() public {}
|
||||
function testFunction3() public {}
|
||||
|
||||
|
||||
function() external [] externalArray0;
|
||||
function() external [] externalArray1;
|
||||
|
||||
function() internal [] internalArray0;
|
||||
function() internal [] internalArray1;
|
||||
|
||||
constructor() {
|
||||
externalArray0 = new function() external[] (3);
|
||||
externalArray1 = [
|
||||
this.testFunction1,
|
||||
this.testFunction2,
|
||||
this.testFunction3
|
||||
];
|
||||
|
||||
internalArray0 = new function() internal[] (3);
|
||||
internalArray1 = [
|
||||
testFunction1,
|
||||
testFunction2,
|
||||
testFunction3
|
||||
];
|
||||
}
|
||||
|
||||
function copyExternalStorageArrayOfFunctionType() external returns (bool) {
|
||||
assert(keccak256(abi.encode(externalArray0)) != keccak256(abi.encode(externalArray1)));
|
||||
externalArray0 = externalArray1;
|
||||
return keccak256(abi.encode(externalArray0)) == keccak256(abi.encode(externalArray1));
|
||||
}
|
||||
|
||||
function copyInternalArrayOfFunctionType() external returns (bool) {
|
||||
internalArray0 = internalArray1;
|
||||
assert(internalArray0.length == 3);
|
||||
|
||||
return
|
||||
internalArray0.length == internalArray1.length &&
|
||||
internalArray0[0] == internalArray1[0] &&
|
||||
internalArray0[1] == internalArray1[1] &&
|
||||
internalArray0[2] == internalArray1[2];
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// copyExternalStorageArrayOfFunctionType() -> true
|
||||
// gas irOptimized: 104701
|
||||
// gas legacy: 108725
|
||||
// gas legacyOptimized: 102441
|
||||
// copyInternalArrayOfFunctionType() -> true
|
@ -0,0 +1,57 @@
|
||||
contract C {
|
||||
function testFunction1() public {}
|
||||
function testFunction2() public view {}
|
||||
function testFunction3() public pure {}
|
||||
|
||||
|
||||
function() external [] externalArray0;
|
||||
function() external [] externalArray1;
|
||||
|
||||
function() internal [] internalArray0;
|
||||
function() internal [] internalArray1;
|
||||
|
||||
constructor() {
|
||||
externalArray0 = new function() external[] (3);
|
||||
externalArray1 = [
|
||||
this.testFunction1,
|
||||
this.testFunction2,
|
||||
this.testFunction3
|
||||
];
|
||||
|
||||
internalArray0 = new function() internal[] (3);
|
||||
internalArray1 = [
|
||||
testFunction1,
|
||||
testFunction2,
|
||||
testFunction3
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
function copyExternalStorageArraysOfFunctionType() external returns (bool)
|
||||
{
|
||||
assert(keccak256(abi.encodePacked(externalArray0)) != keccak256(abi.encodePacked(externalArray1)));
|
||||
externalArray0 = externalArray1;
|
||||
return keccak256(abi.encodePacked(externalArray0)) == keccak256(abi.encodePacked(externalArray1));
|
||||
}
|
||||
|
||||
function copyInternalArrayOfFunctionType() external returns (bool)
|
||||
{
|
||||
internalArray0 = internalArray1;
|
||||
assert(internalArray0.length == 3);
|
||||
|
||||
return
|
||||
internalArray0.length == internalArray1.length &&
|
||||
internalArray0[0] == internalArray1[0] &&
|
||||
internalArray0[1] == internalArray1[1] &&
|
||||
internalArray0[2] == internalArray1[2];
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// copyExternalStorageArraysOfFunctionType() -> true
|
||||
// gas irOptimized: 104372
|
||||
// gas legacy: 108462
|
||||
// gas legacyOptimized: 102174
|
||||
// copyInternalArrayOfFunctionType() -> true
|
||||
// gas legacy: 104178
|
@ -18,6 +18,6 @@ contract C {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// test() -> 7
|
||||
// gas irOptimized: 126552
|
||||
// gas irOptimized: 124080
|
||||
// gas legacy: 205196
|
||||
// gas legacyOptimized: 204987
|
@ -0,0 +1,33 @@
|
||||
contract receiver {
|
||||
uint public received;
|
||||
function recv(uint x) public { received += x + 1; }
|
||||
fallback() external { received = 0x80; }
|
||||
}
|
||||
contract sender {
|
||||
constructor() { rec = new receiver(); }
|
||||
fallback() external { savedData1 = savedData2 = msg.data; }
|
||||
function forward(bool selector) public returns (bool) {
|
||||
if (selector) { address(rec).call(savedData1); delete savedData1; }
|
||||
else { address(rec).call(savedData2); delete savedData2; }
|
||||
return true;
|
||||
}
|
||||
function val() public returns (uint) { return rec.received(); }
|
||||
receiver rec;
|
||||
bytes savedData1;
|
||||
bytes savedData2;
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// (): 7 ->
|
||||
// gas irOptimized: 110941
|
||||
// gas legacy: 111082
|
||||
// gas legacyOptimized: 111027
|
||||
// val() -> 0
|
||||
// forward(bool): true -> true
|
||||
// val() -> 0x80
|
||||
// forward(bool): false -> true
|
||||
// val() -> 0x80
|
||||
// forward(bool): true -> true
|
||||
// val() -> 0x80
|
@ -0,0 +1,19 @@
|
||||
contract c {
|
||||
function set() public returns (bool) { data = msg.data; return true; }
|
||||
function checkIfDataIsEmpty() public returns (bool) { return data.length == 0; }
|
||||
function sendMessage() public returns (bool, bytes memory) { bytes memory emptyData; return address(this).call(emptyData);}
|
||||
fallback() external { data = msg.data; }
|
||||
bytes data;
|
||||
}
|
||||
// ====
|
||||
// EVMVersion: >=byzantium
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// (): 1, 2, 3, 4, 5 ->
|
||||
// gas irOptimized: 155178
|
||||
// gas legacy: 155254
|
||||
// gas legacyOptimized: 155217
|
||||
// checkIfDataIsEmpty() -> false
|
||||
// sendMessage() -> true, 0x40, 0
|
||||
// checkIfDataIsEmpty() -> true
|
41
test/libsolidity/semanticTests/enums/enum_referencing.sol
Normal file
41
test/libsolidity/semanticTests/enums/enum_referencing.sol
Normal file
@ -0,0 +1,41 @@
|
||||
interface I {
|
||||
enum Direction { A, B, Left, Right }
|
||||
}
|
||||
library L {
|
||||
enum Direction { Left, Right }
|
||||
function f() public pure returns (Direction) {
|
||||
return Direction.Right;
|
||||
}
|
||||
function g() public pure returns (I.Direction) {
|
||||
return I.Direction.Right;
|
||||
}
|
||||
}
|
||||
contract C is I {
|
||||
function f() public pure returns (Direction) {
|
||||
return Direction.Right;
|
||||
}
|
||||
function g() public pure returns (I.Direction) {
|
||||
return I.Direction.Right;
|
||||
}
|
||||
function h() public pure returns (L.Direction) {
|
||||
return L.Direction.Right;
|
||||
}
|
||||
function x() public pure returns (L.Direction) {
|
||||
return L.f();
|
||||
}
|
||||
function y() public pure returns (I.Direction) {
|
||||
return L.g();
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// compileToEwasm: false
|
||||
// ----
|
||||
// library: L
|
||||
// f() -> 3
|
||||
// g() -> 3
|
||||
// f() -> 3
|
||||
// g() -> 3
|
||||
// h() -> 1
|
||||
// x() -> 1
|
||||
// y() -> 3
|
@ -0,0 +1,27 @@
|
||||
contract receiver {
|
||||
uint256 public received;
|
||||
function recv(uint256 x) public { received += x + 1; }
|
||||
fallback() external { received = 0x80; }
|
||||
}
|
||||
contract sender {
|
||||
constructor() { rec = new receiver();}
|
||||
fallback() external { savedData = msg.data; }
|
||||
function forward() public returns (bool) { address(rec).call(savedData); return true; }
|
||||
function clear() public returns (bool) { delete savedData; return true; }
|
||||
function val() public returns (uint) { return rec.received(); }
|
||||
receiver rec;
|
||||
bytes savedData;
|
||||
}
|
||||
// ====
|
||||
// allowNonExistingFunctions: true
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// recv(uint256): 7 ->
|
||||
// val() -> 0
|
||||
// forward() -> true
|
||||
// val() -> 8
|
||||
// clear() -> true
|
||||
// val() -> 8
|
||||
// forward() -> true
|
||||
// val() -> 0x80
|
@ -0,0 +1,16 @@
|
||||
library L {
|
||||
function g(function() internal returns (uint) _t) internal returns (uint) { return _t(); }
|
||||
}
|
||||
contract C {
|
||||
using L for *;
|
||||
function f() public returns (uint) {
|
||||
return t.g();
|
||||
}
|
||||
function t() public pure returns (uint) { return 7; }
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: L
|
||||
// f() -> 7
|
@ -0,0 +1,16 @@
|
||||
library D { struct s { uint a; } function mul(s storage self, uint x) public returns (uint) { return self.a *= x; } }
|
||||
contract C {
|
||||
using D for D.s;
|
||||
D.s public x;
|
||||
function f(uint a) public returns (uint) {
|
||||
x.a = 6;
|
||||
return (x.mul)({x: a});
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: D
|
||||
// f(uint256): 7 -> 0x2a
|
||||
// x() -> 0x2a
|
@ -0,0 +1,20 @@
|
||||
library D { function length(string memory self) public returns (uint) { return bytes(self).length; } }
|
||||
contract C {
|
||||
using D for string;
|
||||
string x;
|
||||
function f() public returns (uint) {
|
||||
x = "abc";
|
||||
return x.length();
|
||||
}
|
||||
function g() public returns (uint) {
|
||||
string memory s = "abc";
|
||||
return s.length();
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: D
|
||||
// f() -> 3
|
||||
// g() -> 3
|
@ -0,0 +1,30 @@
|
||||
library Lib {
|
||||
function find(uint16[] storage _haystack, uint16 _needle) public view returns (uint)
|
||||
{
|
||||
for (uint i = 0; i < _haystack.length; ++i)
|
||||
if (_haystack[i] == _needle)
|
||||
return i;
|
||||
return type(uint).max;
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(string => uint16[]) data;
|
||||
function f() public returns (uint a, uint b)
|
||||
{
|
||||
while (data["abc"].length < 20)
|
||||
data["abc"].push();
|
||||
data["abc"][4] = 9;
|
||||
data["abc"][17] = 3;
|
||||
a = Lib.find(data["abc"], 9);
|
||||
b = Lib.find(data["abc"], 3);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: Lib
|
||||
// f() -> 4, 0x11
|
||||
// gas irOptimized: 115822
|
||||
// gas legacy: 135952
|
||||
// gas legacyOptimized: 119643
|
@ -0,0 +1,15 @@
|
||||
library Lib { function m() public returns (address) { return msg.sender; } }
|
||||
contract Test {
|
||||
address public sender;
|
||||
function f() public {
|
||||
sender = Lib.m();
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// compileToEwasm: false
|
||||
// EVMVersion: >=homestead
|
||||
// ----
|
||||
// library: Lib
|
||||
// f() ->
|
||||
// sender() -> 0x1212121212121212121212121212120000000012
|
@ -0,0 +1,14 @@
|
||||
library Lib { function m(uint x, uint y) public returns (uint) { return x * y; } }
|
||||
contract Test {
|
||||
function f(uint x) public returns (uint) {
|
||||
Lib;
|
||||
Lib.m;
|
||||
return x + 9;
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: Lib
|
||||
// f(uint256): 33 -> 0x2a
|
@ -0,0 +1,41 @@
|
||||
library Lib {
|
||||
function set(mapping(uint => uint) storage m, uint key, uint value) internal
|
||||
{
|
||||
m[key] = value;
|
||||
}
|
||||
function get(mapping(uint => uint) storage m, uint key) internal view returns (uint)
|
||||
{
|
||||
return m[key];
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(uint => uint) m;
|
||||
function set(uint256 key, uint256 value) public returns (uint)
|
||||
{
|
||||
uint oldValue = Lib.get(m, key);
|
||||
Lib.set(m, key, value);
|
||||
return oldValue;
|
||||
}
|
||||
function get(uint256 key) public view returns (uint) {
|
||||
return Lib.get(m, key);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: Lib
|
||||
// set(uint256,uint256): 1, 42 -> 0
|
||||
// set(uint256,uint256): 2, 84 -> 0
|
||||
// set(uint256,uint256): 21, 7 -> 0
|
||||
// get(uint256): 0 -> 0
|
||||
// get(uint256): 1 -> 0x2a
|
||||
// get(uint256): 2 -> 0x54
|
||||
// get(uint256): 21 -> 7
|
||||
// set(uint256,uint256): 1, 21 -> 0x2a
|
||||
// set(uint256,uint256): 2, 42 -> 0x54
|
||||
// set(uint256,uint256): 21, 14 -> 7
|
||||
// get(uint256): 0 -> 0
|
||||
// get(uint256): 1 -> 0x15
|
||||
// get(uint256): 2 -> 0x2a
|
||||
// get(uint256): 21 -> 14
|
@ -0,0 +1,75 @@
|
||||
library Lib {
|
||||
function choose_mapping(mapping(uint => uint) storage a, mapping(uint => uint) storage b, bool c) internal pure returns(mapping(uint=>uint) storage)
|
||||
{
|
||||
return c ? a : b;
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(uint => uint) a;
|
||||
mapping(uint => uint) b;
|
||||
function set(bool choice, uint256 key, uint256 value) public returns (uint)
|
||||
{
|
||||
mapping(uint => uint) storage m = Lib.choose_mapping(a, b, choice);
|
||||
uint oldValue = m[key];
|
||||
m[key] = value;
|
||||
return oldValue;
|
||||
}
|
||||
function get(bool choice, uint256 key) public view returns (uint) {
|
||||
return Lib.choose_mapping(a, b, choice)[key];
|
||||
}
|
||||
function get_a(uint256 key) public view returns (uint) {
|
||||
return a[key];
|
||||
}
|
||||
function get_b(uint256 key) public view returns (uint) {
|
||||
return b[key];
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: Lib
|
||||
// set(bool,uint256,uint256): true, 1, 42 -> 0
|
||||
// set(bool,uint256,uint256): true, 2, 84 -> 0
|
||||
// set(bool,uint256,uint256): true, 21, 7 -> 0
|
||||
// set(bool,uint256,uint256): false, 1, 10 -> 0
|
||||
// set(bool,uint256,uint256): false, 2, 11 -> 0
|
||||
// set(bool,uint256,uint256): false, 21, 12 -> 0
|
||||
// get(bool,uint256): true, 0 -> 0
|
||||
// get(bool,uint256): true, 1 -> 0x2a
|
||||
// get(bool,uint256): true, 2 -> 0x54
|
||||
// get(bool,uint256): true, 21 -> 7
|
||||
// get_a(uint256): 0 -> 0
|
||||
// get_a(uint256): 1 -> 0x2a
|
||||
// get_a(uint256): 2 -> 0x54
|
||||
// get_a(uint256): 21 -> 7
|
||||
// get(bool,uint256): false, 0 -> 0
|
||||
// get(bool,uint256): false, 1 -> 10
|
||||
// get(bool,uint256): false, 2 -> 11
|
||||
// get(bool,uint256): false, 21 -> 12
|
||||
// get_b(uint256): 0 -> 0
|
||||
// get_b(uint256): 1 -> 10
|
||||
// get_b(uint256): 2 -> 11
|
||||
// get_b(uint256): 21 -> 12
|
||||
// set(bool,uint256,uint256): true, 1, 21 -> 0x2a
|
||||
// set(bool,uint256,uint256): true, 2, 42 -> 0x54
|
||||
// set(bool,uint256,uint256): true, 21, 14 -> 7
|
||||
// set(bool,uint256,uint256): false, 1, 30 -> 10
|
||||
// set(bool,uint256,uint256): false, 2, 31 -> 11
|
||||
// set(bool,uint256,uint256): false, 21, 32 -> 12
|
||||
// get_a(uint256): 0 -> 0
|
||||
// get_a(uint256): 1 -> 0x15
|
||||
// get_a(uint256): 2 -> 0x2a
|
||||
// get_a(uint256): 21 -> 14
|
||||
// get(bool,uint256): true, 0 -> 0
|
||||
// get(bool,uint256): true, 1 -> 0x15
|
||||
// get(bool,uint256): true, 2 -> 0x2a
|
||||
// get(bool,uint256): true, 21 -> 14
|
||||
// get_b(uint256): 0 -> 0
|
||||
// get_b(uint256): 1 -> 0x1e
|
||||
// get_b(uint256): 2 -> 0x1f
|
||||
// get_b(uint256): 21 -> 0x20
|
||||
// get(bool,uint256): false, 0 -> 0
|
||||
// get(bool,uint256): false, 1 -> 0x1e
|
||||
// get(bool,uint256): false, 2 -> 0x1f
|
||||
// get(bool,uint256): false, 21 -> 0x20
|
@ -0,0 +1,31 @@
|
||||
library Lib {
|
||||
function f(mapping(uint => uint) storage a, mapping(uint => uint) storage b) internal returns(mapping(uint=>uint) storage r)
|
||||
{
|
||||
r = a;
|
||||
r[1] = 42;
|
||||
r = b;
|
||||
r[1] = 21;
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(uint => uint) a;
|
||||
mapping(uint => uint) b;
|
||||
function f() public returns (uint, uint, uint, uint, uint, uint)
|
||||
{
|
||||
Lib.f(a, b)[2] = 84;
|
||||
return (a[0], a[1], a[2], b[0], b[1], b[2]);
|
||||
}
|
||||
function g() public returns (uint, uint, uint, uint, uint, uint)
|
||||
{
|
||||
mapping(uint => uint) storage m = Lib.f(a, b);
|
||||
m[2] = 17;
|
||||
return (a[0], a[1], a[2], b[0], b[1], b[2]);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: Lib
|
||||
// f() -> 0, 0x2a, 0, 0, 0x15, 0x54
|
||||
// g() -> 0, 0x2a, 0, 0, 0x15, 0x11
|
@ -0,0 +1,14 @@
|
||||
library L {
|
||||
function f() public returns (uint) { return 7; }
|
||||
}
|
||||
contract C {
|
||||
function f() public payable returns (uint) {
|
||||
return L.f();
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: L
|
||||
// f(): 27 -> 7
|
@ -0,0 +1,16 @@
|
||||
library D { struct s { uint a; } function mul(s storage self, uint x) public returns (uint) { return self.a *= x; } }
|
||||
contract C {
|
||||
using D for D.s;
|
||||
D.s public x;
|
||||
function f(uint a) public returns (uint) {
|
||||
x.a = 6;
|
||||
return x.mul({x: a});
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: D
|
||||
// f(uint256): 7 -> 0x2a
|
||||
// x() -> 0x2a
|
@ -0,0 +1,20 @@
|
||||
library D {
|
||||
struct s { uint a; }
|
||||
function mul(s storage self, uint x) public returns (uint) { return self.a *= x; }
|
||||
function mul(s storage self, bytes32 x) public returns (bytes32) { }
|
||||
}
|
||||
contract C {
|
||||
using D for D.s;
|
||||
D.s public x;
|
||||
function f(uint a) public returns (uint) {
|
||||
x.a = 6;
|
||||
return x.mul(a);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: D
|
||||
// f(uint256): 7 -> 0x2a
|
||||
// x() -> 0x2a
|
@ -0,0 +1,27 @@
|
||||
library Lib {
|
||||
function set(mapping(uint => uint) storage m, uint key, uint value) public
|
||||
{
|
||||
m[key] = value;
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(uint => uint) m1;
|
||||
mapping(uint => uint) m2;
|
||||
function f() public returns (uint, uint, uint, uint, uint, uint)
|
||||
{
|
||||
Lib.set(m1, 0, 1);
|
||||
Lib.set(m1, 2, 42);
|
||||
Lib.set(m2, 0, 23);
|
||||
Lib.set(m2, 2, 99);
|
||||
return (m1[0], m1[1], m1[2], m2[0], m2[1], m2[2]);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: Lib
|
||||
// f() -> 1, 0, 0x2a, 0x17, 0, 0x63
|
||||
// gas irOptimized: 119757
|
||||
// gas legacy: 124793
|
||||
// gas legacyOptimized: 119694
|
@ -0,0 +1,25 @@
|
||||
library Lib {
|
||||
function choose(mapping(uint => mapping(uint => uint)) storage m, uint key) external returns (mapping(uint => uint) storage) {
|
||||
return m[key];
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(uint => mapping(uint => uint)) m;
|
||||
function f() public returns (uint, uint, uint, uint, uint, uint)
|
||||
{
|
||||
Lib.choose(m, 0)[0] = 1;
|
||||
Lib.choose(m, 0)[2] = 42;
|
||||
Lib.choose(m, 1)[0] = 23;
|
||||
Lib.choose(m, 1)[2] = 99;
|
||||
return (m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2]);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: Lib
|
||||
// f() -> 1, 0, 0x2a, 0x17, 0, 0x63
|
||||
// gas irOptimized: 120471
|
||||
// gas legacy: 125245
|
||||
// gas legacyOptimized: 120153
|
@ -0,0 +1,27 @@
|
||||
library Lib {
|
||||
struct Data { uint a; uint[] b; }
|
||||
function set(Data storage _s) public
|
||||
{
|
||||
_s.a = 7;
|
||||
while (_s.b.length < 20)
|
||||
_s.b.push();
|
||||
_s.b[19] = 8;
|
||||
}
|
||||
}
|
||||
contract Test {
|
||||
mapping(string => Lib.Data) data;
|
||||
function f() public returns (uint a, uint b)
|
||||
{
|
||||
Lib.set(data["abc"]);
|
||||
a = data["abc"].a;
|
||||
b = data["abc"].b[19];
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: Lib
|
||||
// f() -> 7, 8
|
||||
// gas irOptimized: 101869
|
||||
// gas legacy: 101504
|
@ -0,0 +1,61 @@
|
||||
pragma abicoder v2;
|
||||
interface I {
|
||||
struct S { uint a; }
|
||||
}
|
||||
|
||||
library L {
|
||||
struct S { uint b; uint a; }
|
||||
function f() public pure returns (S memory) {
|
||||
S memory s;
|
||||
s.a = 3;
|
||||
return s;
|
||||
}
|
||||
function g() public pure returns (I.S memory) {
|
||||
I.S memory s;
|
||||
s.a = 4;
|
||||
return s;
|
||||
}
|
||||
// argument-dependant lookup tests
|
||||
function a(I.S memory) public pure returns (uint) { return 1; }
|
||||
function a(S memory) public pure returns (uint) { return 2; }
|
||||
}
|
||||
|
||||
contract C is I {
|
||||
function f() public pure returns (S memory) {
|
||||
S memory s;
|
||||
s.a = 1;
|
||||
return s;
|
||||
}
|
||||
function g() public pure returns (I.S memory) {
|
||||
I.S memory s;
|
||||
s.a = 2;
|
||||
return s;
|
||||
}
|
||||
function h() public pure returns (L.S memory) {
|
||||
L.S memory s;
|
||||
s.a = 5;
|
||||
return s;
|
||||
}
|
||||
function x() public pure returns (L.S memory) {
|
||||
return L.f();
|
||||
}
|
||||
function y() public pure returns (I.S memory) {
|
||||
return L.g();
|
||||
}
|
||||
function a1() public pure returns (uint) { S memory s; return L.a(s); }
|
||||
function a2() public pure returns (uint) { L.S memory s; return L.a(s); }
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: L
|
||||
// f() -> 1
|
||||
// g() -> 2
|
||||
// f() -> 1
|
||||
// g() -> 2
|
||||
// h() -> 0, 5
|
||||
// x() -> 0, 3
|
||||
// y() -> 4
|
||||
// a1() -> 1
|
||||
// a2() -> 2
|
@ -0,0 +1,16 @@
|
||||
library D { struct s { uint a; } function mul(s storage self, uint x) public returns (uint) { return self.a *= x; } }
|
||||
contract C {
|
||||
using D for D.s;
|
||||
D.s public x;
|
||||
function f(uint a) public returns (uint) {
|
||||
x.a = 3;
|
||||
return x.mul(a);
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileToEwasm: false
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// library: D
|
||||
// f(uint256): 7 -> 0x15
|
||||
// x() -> 0x15
|
@ -0,0 +1,26 @@
|
||||
contract C {
|
||||
function callMeMaybe(uint a, uint b, uint[] memory c) external {}
|
||||
|
||||
function abiEncodeSimple(uint x, uint y, uint z, uint[] memory a, uint[] memory b) public view {
|
||||
require(x == y);
|
||||
bytes memory b1 = abi.encodeCall(this.callMeMaybe, (x, z, a));
|
||||
bytes memory b2 = abi.encodeCall(this.callMeMaybe, (y, z, a));
|
||||
assert(b1.length == b2.length);
|
||||
|
||||
bytes memory b3 = abi.encodeCall(this.callMeMaybe, (y, z, b));
|
||||
assert(b1.length == b3.length); // should fail
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// SMTEngine: all
|
||||
// SMTIgnoreCex: yes
|
||||
// ----
|
||||
// Warning 6031: (233-249): Internal error: Expression undefined for SMT solver.
|
||||
// Warning 6031: (298-314): Internal error: Expression undefined for SMT solver.
|
||||
// Warning 6031: (398-414): Internal error: Expression undefined for SMT solver.
|
||||
// Warning 1218: (330-360): CHC: Error trying to invoke SMT solver.
|
||||
// Warning 1218: (430-460): CHC: Error trying to invoke SMT solver.
|
||||
// Warning 6328: (330-360): CHC: Assertion violation might happen here.
|
||||
// Warning 6328: (430-460): CHC: Assertion violation might happen here.
|
||||
// Warning 4661: (330-360): BMC: Assertion violation happens here.
|
||||
// Warning 4661: (430-460): BMC: Assertion violation happens here.
|
@ -0,0 +1,9 @@
|
||||
contract C {
|
||||
function() external [] s0;
|
||||
function() external view [] s1;
|
||||
function copyStorageArrayOfFunctionType() public {
|
||||
s1 = s0;
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError 7407: (148-150): Type function () external[] storage ref is not implicitly convertible to expected type function () view external[] storage ref.
|
82
test/libsolidity/syntaxTests/specialFunctions/encodeCall.sol
Normal file
82
test/libsolidity/syntaxTests/specialFunctions/encodeCall.sol
Normal file
@ -0,0 +1,82 @@
|
||||
interface I {
|
||||
function fExternal(uint256 p, string memory t) external;
|
||||
}
|
||||
|
||||
library L {
|
||||
function fExternal(uint256 p, string memory t) external {}
|
||||
}
|
||||
|
||||
contract C {
|
||||
using L for uint256;
|
||||
|
||||
function f(int a) public {}
|
||||
function f2(int a, string memory b) public {}
|
||||
function f3(int a, int b) public {}
|
||||
function f4() public {}
|
||||
function fInternal(uint256 p, string memory t) internal {}
|
||||
|
||||
function failFunctionArgsWrongType() public returns(bytes memory) {
|
||||
return abi.encodeCall(this.f, ("test"));
|
||||
}
|
||||
function failFunctionArgsTooMany() public returns(bytes memory) {
|
||||
return abi.encodeCall(this.f, (1, 2));
|
||||
}
|
||||
function failFunctionArgsTooFew0() public returns(bytes memory) {
|
||||
return abi.encodeCall(this.f, ());
|
||||
}
|
||||
function failFunctionArgsTooFew1() public returns(bytes memory) {
|
||||
return abi.encodeCall(this.f);
|
||||
}
|
||||
function failFunctionPtrMissing() public returns(bytes memory) {
|
||||
return abi.encodeCall(1, this.f);
|
||||
}
|
||||
function failFunctionPtrWrongType() public returns(bytes memory) {
|
||||
return abi.encodeCall(abi.encodeCall, (1, 2, 3, "test"));
|
||||
}
|
||||
function failFunctionInternal() public returns(bytes memory) {
|
||||
return abi.encodeCall(fInternal, (1, "123"));
|
||||
}
|
||||
function failFunctionInternalFromVariable() public returns(bytes memory) {
|
||||
function(uint256, string memory) internal localFunctionPointer = fInternal;
|
||||
return abi.encodeCall(localFunctionPointer, (1, "123"));
|
||||
}
|
||||
function failFunctionArgsArrayLiteral() public returns(bytes memory) {
|
||||
return abi.encodeCall(this.f3, [1, 2]);
|
||||
}
|
||||
function failLibraryPointerCall() public returns (bytes memory) {
|
||||
return abi.encodeCall(L.fExternal, (1, "123"));
|
||||
}
|
||||
function failBoundLibraryPointerCall() public returns (bytes memory) {
|
||||
uint256 x = 1;
|
||||
return abi.encodeCall(x.fExternal, (1, "123"));
|
||||
}
|
||||
function failInterfacePointerCall() public returns (bytes memory) {
|
||||
return abi.encodeCall(I.fExternal, (1, "123"));
|
||||
}
|
||||
function successFunctionArgsIntLiteralTuple() public returns(bytes memory) {
|
||||
return abi.encodeCall(this.f, (1));
|
||||
}
|
||||
function successFunctionArgsIntLiteral() public returns(bytes memory) {
|
||||
return abi.encodeCall(this.f, 1);
|
||||
}
|
||||
function successFunctionArgsLiteralTuple() public returns(bytes memory) {
|
||||
return abi.encodeCall(this.f2, (1, "test"));
|
||||
}
|
||||
function successFunctionArgsEmptyTuple() public returns(bytes memory) {
|
||||
return abi.encodeCall(this.f4, ());
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError 5407: (486-494): Cannot implicitly convert component at position 0 from "literal_string "test"" to "int256".
|
||||
// TypeError 7788: (576-606): Expected 1 instead of 2 components for the tuple parameter.
|
||||
// TypeError 7788: (687-713): Expected 1 instead of 0 components for the tuple parameter.
|
||||
// TypeError 6219: (794-816): Expected two arguments: a function pointer followed by a tuple.
|
||||
// TypeError 5511: (911-912): Expected first argument to be a function pointer, not "int_const 1".
|
||||
// TypeError 3509: (1018-1032): Function must be "public" or "external".
|
||||
// TypeError 3509: (1145-1154): Function must be "public" or "external". Did you forget to prefix "this."?
|
||||
// TypeError 3509: (1350-1370): Function must be "public" or "external".
|
||||
// TypeError 7515: (1469-1500): Expected a tuple with 2 components instead of a single non-tuple parameter.
|
||||
// TypeError 5407: (1493-1499): Cannot implicitly convert component at position 0 from "uint8[2]" to "int256".
|
||||
// TypeError 3509: (1596-1607): Function must be "public" or "external".
|
||||
// TypeError 3509: (1738-1749): Function must be "public" or "external".
|
||||
// TypeError 3509: (1860-1871): Function must be "public" or "external".
|
@ -0,0 +1,9 @@
|
||||
contract C {
|
||||
function f(int a, int b) public {}
|
||||
function failFunctionArgsIntLiteralNestedTuple() public returns(bytes memory) {
|
||||
return abi.encodeCall(this.f, ((1,2)));
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError 7788: (139-170): Expected 2 instead of 1 components for the tuple parameter.
|
||||
// TypeError 5407: (163-168): Cannot implicitly convert component at position 0 from "tuple(int_const 1,int_const 2)" to "int256".
|
@ -0,0 +1,8 @@
|
||||
contract C {
|
||||
function f(int a) public {}
|
||||
function failFunctionArgsIntLiteralTuple() public returns(bytes memory) {
|
||||
return abi.encodeCall(this.f, (1,));
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// TypeError 8381: (149-153): Tuple component cannot be empty.
|
874
test/lsp.py
Executable file
874
test/lsp.py
Executable file
@ -0,0 +1,874 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import fnmatch
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
import colorama # Enables the use of SGR & CUP terminal VT sequences on Windows.
|
||||
from deepdiff import DeepDiff
|
||||
|
||||
# {{{ JsonRpcProcess
|
||||
class BadHeader(Exception):
|
||||
def __init__(self, msg: str):
|
||||
super().__init__("Bad header: " + msg)
|
||||
|
||||
class JsonRpcProcess:
|
||||
exe_path: str
|
||||
exe_args: List[str]
|
||||
process: subprocess.Popen
|
||||
trace_io: bool
|
||||
|
||||
def __init__(self, exe_path: str, exe_args: List[str], trace_io: bool = True):
|
||||
self.exe_path = exe_path
|
||||
self.exe_args = exe_args
|
||||
self.trace_io = trace_io
|
||||
|
||||
def __enter__(self):
|
||||
self.process = subprocess.Popen(
|
||||
[self.exe_path, *self.exe_args],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE
|
||||
)
|
||||
return self
|
||||
|
||||
def __exit__(self, exception_type, exception_value, traceback) -> None:
|
||||
self.process.kill()
|
||||
self.process.wait(timeout=2.0)
|
||||
|
||||
def trace(self, topic: str, message: str) -> None:
|
||||
if self.trace_io:
|
||||
print(f"{SGR_TRACE}{topic}:{SGR_RESET} {message}")
|
||||
|
||||
def receive_message(self) -> Union[None, dict]:
|
||||
# Note, we should make use of timeout to avoid infinite blocking if nothing is received.
|
||||
CONTENT_LENGTH_HEADER = "Content-Length: "
|
||||
CONTENT_TYPE_HEADER = "Content-Type: "
|
||||
if self.process.stdout == None:
|
||||
return None
|
||||
message_size = None
|
||||
while True:
|
||||
# read header
|
||||
line = self.process.stdout.readline()
|
||||
if line == '':
|
||||
# server quit
|
||||
return None
|
||||
line = line.decode("utf-8")
|
||||
if not line.endswith("\r\n"):
|
||||
raise BadHeader("missing newline")
|
||||
# remove the "\r\n"
|
||||
line = line[:-2]
|
||||
if line == '':
|
||||
break # done with the headers
|
||||
if line.startswith(CONTENT_LENGTH_HEADER):
|
||||
line = line[len(CONTENT_LENGTH_HEADER):]
|
||||
if not line.isdigit():
|
||||
raise BadHeader("size is not int")
|
||||
message_size = int(line)
|
||||
elif line.startswith(CONTENT_TYPE_HEADER):
|
||||
# nothing todo with type for now.
|
||||
pass
|
||||
else:
|
||||
raise BadHeader("unknown header")
|
||||
if message_size is None:
|
||||
raise BadHeader("missing size")
|
||||
rpc_message = self.process.stdout.read(message_size).decode("utf-8")
|
||||
json_object = json.loads(rpc_message)
|
||||
self.trace('receive_message', json.dumps(json_object, indent=4, sort_keys=True))
|
||||
return json_object
|
||||
|
||||
def send_message(self, method_name: str, params: Optional[dict]) -> None:
|
||||
if self.process.stdin == None:
|
||||
return
|
||||
message = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': method_name,
|
||||
'params': params
|
||||
}
|
||||
json_string = json.dumps(obj=message)
|
||||
rpc_message = f"Content-Length: {len(json_string)}\r\n\r\n{json_string}"
|
||||
self.trace(f'send_message ({method_name})', json.dumps(message, indent=4, sort_keys=True))
|
||||
self.process.stdin.write(rpc_message.encode("utf-8"))
|
||||
self.process.stdin.flush()
|
||||
|
||||
def call_method(self, method_name: str, params: Optional[dict]) -> Any:
|
||||
self.send_message(method_name, params)
|
||||
return self.receive_message()
|
||||
|
||||
def send_notification(self, name: str, params: Optional[dict] = None) -> None:
|
||||
self.send_message(name, params)
|
||||
|
||||
# }}}
|
||||
|
||||
SGR_RESET = '\033[m'
|
||||
SGR_TRACE = '\033[1;36m'
|
||||
SGR_NOTICE = '\033[1;35m'
|
||||
SGR_TEST_BEGIN = '\033[1;33m'
|
||||
SGR_ASSERT_BEGIN = '\033[1;34m'
|
||||
SGR_STATUS_OKAY = '\033[1;32m'
|
||||
SGR_STATUS_FAIL = '\033[1;31m'
|
||||
|
||||
class ExpectationFailed(Exception):
|
||||
def __init__(self, actual, expected):
|
||||
self.actual = actual
|
||||
self.expected = expected
|
||||
diff = DeepDiff(actual, expected)
|
||||
super().__init__(
|
||||
f"Expectation failed.\n\tExpected {expected}\n\tbut got {actual}.\n\t{diff}"
|
||||
)
|
||||
|
||||
def create_cli_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Solidity LSP Test suite")
|
||||
parser.set_defaults(trace_io=False)
|
||||
parser.add_argument(
|
||||
"-T, --trace-io",
|
||||
dest="trace_io",
|
||||
action="store_true",
|
||||
help="Be more verbose by also printing assertions."
|
||||
)
|
||||
parser.set_defaults(print_assertions=False)
|
||||
parser.add_argument(
|
||||
"-v, --print-assertions",
|
||||
dest="print_assertions",
|
||||
action="store_true",
|
||||
help="Be more verbose by also printing assertions."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t, --test-pattern",
|
||||
dest="test_pattern",
|
||||
type=str,
|
||||
default="*",
|
||||
help="Filters all available tests by matching against this test pattern (using globbing)",
|
||||
nargs="?"
|
||||
)
|
||||
parser.add_argument(
|
||||
"solc_path",
|
||||
type=str,
|
||||
default="solc",
|
||||
help="Path to solc binary to test against",
|
||||
nargs="?"
|
||||
)
|
||||
parser.add_argument(
|
||||
"project_root_dir",
|
||||
type=str,
|
||||
default=f"{os.path.dirname(os.path.realpath(__file__))}/..",
|
||||
help="Path to Solidity project's root directory (must be fully qualified).",
|
||||
nargs="?"
|
||||
)
|
||||
return parser
|
||||
|
||||
class Counter:
|
||||
total: int = 0
|
||||
passed: int = 0
|
||||
failed: int = 0
|
||||
|
||||
class SolidityLSPTestSuite: # {{{
|
||||
test_counter = Counter()
|
||||
assertion_counter = Counter()
|
||||
print_assertions: bool = False
|
||||
trace_io: bool = False
|
||||
test_pattern: str
|
||||
|
||||
def __init__(self):
|
||||
colorama.init()
|
||||
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.print_assertions = args.print_assertions
|
||||
self.trace_io = args.trace_io
|
||||
self.test_pattern = args.test_pattern
|
||||
|
||||
print(f"{SGR_NOTICE}test pattern: {self.test_pattern}{SGR_RESET}")
|
||||
|
||||
def main(self) -> int:
|
||||
"""
|
||||
Runs all test cases.
|
||||
Returns 0 on success and the number of failing assertions (capped to 127) otherwise.
|
||||
"""
|
||||
all_tests = sorted([
|
||||
str(name)[5:]
|
||||
for name in dir(SolidityLSPTestSuite)
|
||||
if callable(getattr(SolidityLSPTestSuite, name)) and name.startswith("test_")
|
||||
])
|
||||
filtered_tests = fnmatch.filter(all_tests, self.test_pattern)
|
||||
for method_name in filtered_tests:
|
||||
test_fn = getattr(self, 'test_' + method_name)
|
||||
title: str = test_fn.__name__[5:]
|
||||
print(f"{SGR_TEST_BEGIN}Testing {title} ...{SGR_RESET}")
|
||||
try:
|
||||
with JsonRpcProcess(self.solc_path, ["--lsp"], trace_io=self.trace_io) as solc:
|
||||
test_fn(solc)
|
||||
self.test_counter.passed += 1
|
||||
except ExpectationFailed as e:
|
||||
self.test_counter.failed += 1
|
||||
print(e)
|
||||
print(traceback.format_exc())
|
||||
except Exception as e: # pragma pylint: disable=broad-except
|
||||
self.test_counter.failed += 1
|
||||
print(f"Unhandled exception {e.__class__.__name__} caught: {e}")
|
||||
print(traceback.format_exc())
|
||||
|
||||
print(
|
||||
f"\n{SGR_NOTICE}Summary:{SGR_RESET}\n\n"
|
||||
f" Test cases: {self.test_counter.passed} passed, {self.test_counter.failed} failed\n"
|
||||
f" Assertions: {self.assertion_counter.passed} passed, {self.assertion_counter.failed} failed\n"
|
||||
)
|
||||
|
||||
return min(max(self.test_counter.failed, self.assertion_counter.failed), 127)
|
||||
|
||||
def setup_lsp(self, lsp: JsonRpcProcess, expose_project_root=True):
|
||||
"""
|
||||
Prepares the solc LSP server by calling `initialize`,
|
||||
and `initialized` methods.
|
||||
"""
|
||||
params = {
|
||||
'processId': None,
|
||||
'rootUri': self.project_root_uri,
|
||||
'trace': 'off',
|
||||
'initializationOptions': {},
|
||||
'capabilities': {
|
||||
'textDocument': {
|
||||
'publishDiagnostics': {'relatedInformation': True}
|
||||
},
|
||||
'workspace': {
|
||||
'applyEdit': True,
|
||||
'configuration': True,
|
||||
'didChangeConfiguration': {'dynamicRegistration': True},
|
||||
'workspaceEdit': {'documentChanges': True},
|
||||
'workspaceFolders': True
|
||||
}
|
||||
}
|
||||
}
|
||||
if expose_project_root == False:
|
||||
params['rootUri'] = None
|
||||
lsp.call_method('initialize', params)
|
||||
lsp.send_notification('initialized')
|
||||
|
||||
# {{{ helpers
|
||||
def get_test_file_path(self, test_case_name):
|
||||
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)
|
||||
|
||||
def get_test_file_contents(self, test_case_name):
|
||||
"""
|
||||
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).
|
||||
"""
|
||||
with open(self.get_test_file_path(test_case_name), mode="r", encoding="utf-8", newline='') as f:
|
||||
return f.read()
|
||||
|
||||
def require_params_for_method(self, method_name: str, message: dict) -> Any:
|
||||
"""
|
||||
Ensures the given RPC message does contain the
|
||||
field 'method' with the given method name,
|
||||
and then returns its passed params.
|
||||
An exception is raised on expectation failures.
|
||||
"""
|
||||
assert message is not None
|
||||
if 'error' in message.keys():
|
||||
code = message['error']["code"]
|
||||
text = message['error']['message']
|
||||
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")
|
||||
return message['params']
|
||||
|
||||
def wait_for_diagnostics(self, solc: JsonRpcProcess, count: int) -> List[dict]:
|
||||
"""
|
||||
Return `count` number of published diagnostic reports sorted by file URI.
|
||||
"""
|
||||
reports = []
|
||||
for _ in range(0, count):
|
||||
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 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',
|
||||
{
|
||||
'textDocument':
|
||||
{
|
||||
'uri': self.get_test_file_uri(test_case_name),
|
||||
'languageId': 'Solidity',
|
||||
'version': 1,
|
||||
'text': self.get_test_file_contents(test_case_name)
|
||||
}
|
||||
}
|
||||
)
|
||||
return self.wait_for_diagnostics(solc_process, max_diagnostic_reports)
|
||||
|
||||
def expect_equal(self, actual, expected, description="Equality") -> None:
|
||||
self.assertion_counter.total += 1
|
||||
prefix = f"[{self.assertion_counter.total}] {SGR_ASSERT_BEGIN}{description}: "
|
||||
diff = DeepDiff(actual, expected)
|
||||
if len(diff) == 0:
|
||||
self.assertion_counter.passed += 1
|
||||
if self.print_assertions:
|
||||
print(prefix + SGR_STATUS_OKAY + 'OK' + SGR_RESET)
|
||||
return
|
||||
|
||||
# Failed assertions are always printed.
|
||||
self.assertion_counter.failed += 1
|
||||
print(prefix + SGR_STATUS_FAIL + 'FAILED' + SGR_RESET)
|
||||
raise ExpectationFailed(actual, expected)
|
||||
|
||||
def expect_empty_diagnostics(self, published_diagnostics: List[dict]) -> None:
|
||||
self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification")
|
||||
self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0, "should not contain diagnostics")
|
||||
|
||||
def expect_diagnostic(
|
||||
self,
|
||||
diagnostic,
|
||||
code: int,
|
||||
lineNo: int,
|
||||
startEndColumns: Tuple[int, int]
|
||||
):
|
||||
assert len(startEndColumns) == 2
|
||||
[startColumn, endColumn] = startEndColumns
|
||||
self.expect_equal(diagnostic['code'], code, f'diagnostic: {code}')
|
||||
self.expect_equal(
|
||||
diagnostic['range'],
|
||||
{
|
||||
'start': {'character': startColumn, 'line': lineNo},
|
||||
'end': {'character': endColumn, 'line': lineNo}
|
||||
},
|
||||
"diagnostic: check range"
|
||||
)
|
||||
# }}}
|
||||
|
||||
# {{{ 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']
|
||||
|
||||
self.expect_equal(len(diagnostics), 3, "3 diagnostic messages")
|
||||
self.expect_diagnostic(diagnostics[0], code=6321, lineNo=13, startEndColumns=(44, 48))
|
||||
self.expect_diagnostic(diagnostics[1], code=2072, lineNo= 7, startEndColumns=( 8, 19))
|
||||
self.expect_diagnostic(diagnostics[2], code=2072, lineNo=15, startEndColumns=( 8, 20))
|
||||
|
||||
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']
|
||||
|
||||
self.expect_equal(len(diagnostics), 3, "3 diagnostic messages")
|
||||
self.expect_diagnostic(diagnostics[0], code=9574, lineNo= 7, startEndColumns=( 8, 21))
|
||||
self.expect_diagnostic(diagnostics[1], code=6777, lineNo= 8, startEndColumns=( 8, 15))
|
||||
self.expect_diagnostic(diagnostics[2], code=6160, lineNo=18, startEndColumns=(15, 36))
|
||||
|
||||
def test_publish_diagnostics_errors_multiline(self, solc: JsonRpcProcess) -> None:
|
||||
self.setup_lsp(solc)
|
||||
TEST_NAME = 'publish_diagnostics_3'
|
||||
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']
|
||||
|
||||
self.expect_equal(len(diagnostics), 1, "3 diagnostic messages")
|
||||
self.expect_equal(diagnostics[0]['code'], 3656, "diagnostic: check code")
|
||||
self.expect_equal(
|
||||
diagnostics[0]['range'],
|
||||
{
|
||||
'end': {'character': 1, 'line': 9},
|
||||
'start': {'character': 0, 'line': 7}
|
||||
},
|
||||
"diagnostic: check range"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
self.expect_equal(len(published_diagnostics), 2, "Diagnostic reports for 2 files")
|
||||
|
||||
# primary file:
|
||||
report = published_diagnostics[0]
|
||||
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):
|
||||
report = published_diagnostics[1]
|
||||
self.expect_equal(report['uri'], self.get_test_file_uri('lib'), "Correct file URI")
|
||||
self.expect_equal(len(report['diagnostics']), 1, "one diagnostic")
|
||||
self.expect_diagnostic(report['diagnostics'][0], code=2072, lineNo=12, startEndColumns=(8, 19))
|
||||
|
||||
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)
|
||||
self.open_file_and_wait_for_diagnostics(solc, 'lib', 2)
|
||||
solc.send_message(
|
||||
'textDocument/didChange',
|
||||
{
|
||||
'textDocument':
|
||||
{
|
||||
'uri': self.get_test_file_uri('lib')
|
||||
},
|
||||
'contentChanges':
|
||||
[
|
||||
{
|
||||
'range': {
|
||||
'start': { 'line': 5, 'character': 0 },
|
||||
'end': { 'line': 10, 'character': 0 }
|
||||
},
|
||||
'text': "" # deleting function `add`
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
published_diagnostics = self.wait_for_diagnostics(solc, 2)
|
||||
self.expect_equal(len(published_diagnostics), 2, "Diagnostic reports for 2 files")
|
||||
|
||||
# Main file now contains a new diagnostic
|
||||
report = published_diagnostics[0]
|
||||
self.expect_equal(report['uri'], self.get_test_file_uri('didOpen_with_import'))
|
||||
diagnostics = report['diagnostics']
|
||||
self.expect_equal(len(diagnostics), 1, "now, no diagnostics")
|
||||
self.expect_diagnostic(diagnostics[0], code=9582, lineNo=9, startEndColumns=(15, 22))
|
||||
|
||||
# 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(len(report['diagnostics']), 0)
|
||||
# The warning went away because the compiler aborts further processing after the error.
|
||||
|
||||
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)
|
||||
self.verify_didOpen_with_import_diagnostics(published_diagnostics)
|
||||
|
||||
def verify_didOpen_with_import_diagnostics(
|
||||
self,
|
||||
published_diagnostics: List[Any],
|
||||
main_file_name='didOpen_with_import'
|
||||
):
|
||||
self.expect_equal(len(published_diagnostics), 2, "Diagnostic reports for 2 files")
|
||||
|
||||
# primary file:
|
||||
report = published_diagnostics[0]
|
||||
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):
|
||||
report = published_diagnostics[1]
|
||||
self.expect_equal(report['uri'], self.get_test_file_uri('lib'), "Correct file URI")
|
||||
self.expect_equal(len(report['diagnostics']), 1, "one diagnostic")
|
||||
self.expect_diagnostic(report['diagnostics'][0], code=2072, lineNo=12, startEndColumns=(8, 19))
|
||||
|
||||
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)
|
||||
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']
|
||||
self.expect_equal(len(diagnostics), 3, "3 diagnostic messages")
|
||||
self.expect_diagnostic(diagnostics[0], code=6321, lineNo=13, startEndColumns=(44, 48))
|
||||
self.expect_diagnostic(diagnostics[1], code=2072, lineNo= 7, startEndColumns=( 8, 19))
|
||||
self.expect_diagnostic(diagnostics[2], code=2072, lineNo=15, startEndColumns=( 8, 20))
|
||||
|
||||
solc.send_message(
|
||||
'textDocument/didChange',
|
||||
{
|
||||
'textDocument': {
|
||||
'uri': self.get_test_file_uri(TEST_NAME)
|
||||
},
|
||||
'contentChanges': [
|
||||
{
|
||||
'range': {
|
||||
'start': { 'line': 7, 'character': 1 },
|
||||
'end': { 'line': 8, 'character': 1 }
|
||||
},
|
||||
'text': ""
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
published_diagnostics = self.wait_for_diagnostics(solc, 1)
|
||||
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")
|
||||
diagnostics = report['diagnostics']
|
||||
self.expect_equal(len(diagnostics), 2)
|
||||
self.expect_diagnostic(diagnostics[0], code=6321, lineNo=12, startEndColumns=(44, 48))
|
||||
self.expect_diagnostic(diagnostics[1], code=2072, lineNo=14, startEndColumns=( 8, 20))
|
||||
|
||||
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)
|
||||
# lib.sol: Fix the unused variable message by removing it.
|
||||
solc.send_message(
|
||||
'textDocument/didChange',
|
||||
{
|
||||
'textDocument':
|
||||
{
|
||||
'uri': self.get_test_file_uri('lib')
|
||||
},
|
||||
'contentChanges': # delete the in-body statement: `uint unused;`
|
||||
[
|
||||
{
|
||||
'range':
|
||||
{
|
||||
'start': { 'line': 12, 'character': 1 },
|
||||
'end': { 'line': 13, 'character': 1 }
|
||||
},
|
||||
'text': ""
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
published_diagnostics = self.wait_for_diagnostics(solc, 2)
|
||||
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")
|
||||
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(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') }}
|
||||
)
|
||||
|
||||
published_diagnostics = self.wait_for_diagnostics(solc, 2)
|
||||
self.verify_didOpen_with_import_diagnostics(published_diagnostics)
|
||||
|
||||
def test_textDocument_opening_two_new_files_edit_and_close(self, solc: JsonRpcProcess) -> None:
|
||||
"""
|
||||
Open two new files A and B, let A import B, expect no error,
|
||||
then close B and now expect the error of file B not being found.
|
||||
"""
|
||||
|
||||
self.setup_lsp(solc)
|
||||
FILE_A_URI = 'file:///a.sol'
|
||||
solc.send_message('textDocument/didOpen', {
|
||||
'textDocument': {
|
||||
'uri': FILE_A_URI,
|
||||
'languageId': 'Solidity',
|
||||
'version': 1,
|
||||
'text': ''.join([
|
||||
'// SPDX-License-Identifier: UNLICENSED\n',
|
||||
'pragma solidity >=0.8.0;\n',
|
||||
])
|
||||
}
|
||||
})
|
||||
reports = self.wait_for_diagnostics(solc, 1)
|
||||
self.expect_equal(len(reports), 1, "one publish diagnostics notification")
|
||||
self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics")
|
||||
|
||||
FILE_B_URI = 'file:///b.sol'
|
||||
solc.send_message('textDocument/didOpen', {
|
||||
'textDocument': {
|
||||
'uri': FILE_B_URI,
|
||||
'languageId': 'Solidity',
|
||||
'version': 1,
|
||||
'text': ''.join([
|
||||
'// SPDX-License-Identifier: UNLICENSED\n',
|
||||
'pragma solidity >=0.8.0;\n',
|
||||
])
|
||||
}
|
||||
})
|
||||
reports = self.wait_for_diagnostics(solc, 2)
|
||||
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")
|
||||
|
||||
solc.send_message('textDocument/didChange', {
|
||||
'textDocument': {
|
||||
'uri': FILE_A_URI
|
||||
},
|
||||
'contentChanges': [
|
||||
{
|
||||
'range': {
|
||||
'start': { 'line': 2, 'character': 0 },
|
||||
'end': { 'line': 2, 'character': 0 }
|
||||
},
|
||||
'text': 'import "./b.sol";\n'
|
||||
}
|
||||
]
|
||||
})
|
||||
reports = self.wait_for_diagnostics(solc, 2)
|
||||
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")
|
||||
|
||||
solc.send_message(
|
||||
'textDocument/didClose',
|
||||
{ '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)
|
||||
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")
|
||||
|
||||
def test_textDocument_closing_virtual_file_removes_imported_real_file(self, solc: JsonRpcProcess) -> None:
|
||||
"""
|
||||
We open a virtual file that imports a real file with a warning.
|
||||
Once we close the virtual file, the warning is removed from the diagnostics,
|
||||
since the real file is not considered part of the project anymore.
|
||||
"""
|
||||
|
||||
self.setup_lsp(solc)
|
||||
FILE_A_URI = f'file://{self.project_root_dir}/a.sol'
|
||||
solc.send_message('textDocument/didOpen', {
|
||||
'textDocument': {
|
||||
'uri': FILE_A_URI,
|
||||
'languageId': 'Solidity',
|
||||
'version': 1,
|
||||
'text':
|
||||
'// SPDX-License-Identifier: UNLICENSED\n'
|
||||
'pragma solidity >=0.8.0;\n'
|
||||
'import "./lib.sol";\n'
|
||||
}
|
||||
})
|
||||
reports = self.wait_for_diagnostics(solc, 2)
|
||||
self.expect_equal(len(reports), 2, '')
|
||||
self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics")
|
||||
self.expect_diagnostic(reports[1]['diagnostics'][0], 2072, 12, (8, 19)) # unused variable in lib.sol
|
||||
|
||||
# Now close the file and expect the warning for lib.sol to be removed
|
||||
solc.send_message(
|
||||
'textDocument/didClose',
|
||||
{ 'textDocument': { 'uri': FILE_A_URI }}
|
||||
)
|
||||
reports = self.wait_for_diagnostics(solc, 1)
|
||||
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.
|
||||
"""
|
||||
self.setup_lsp(solc)
|
||||
FILE_NAME = 'didChange_template'
|
||||
FILE_URI = self.get_test_file_uri(FILE_NAME)
|
||||
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, 1)
|
||||
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', {
|
||||
'textDocument': {
|
||||
'uri': FILE_URI
|
||||
},
|
||||
'contentChanges': [
|
||||
{
|
||||
'range': {
|
||||
'start': { 'line': 6, 'character': 0 },
|
||||
'end': { 'line': 6, 'character': 0 }
|
||||
},
|
||||
'text': " f"
|
||||
}
|
||||
]
|
||||
})
|
||||
published_diagnostics = self.wait_for_diagnostics(solc, 1)
|
||||
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")
|
||||
self.expect_equal(len(report2['diagnostics']), 1, "one diagnostic")
|
||||
self.expect_diagnostic(report2['diagnostics'][0], 7858, 6, (1, 2))
|
||||
|
||||
solc.send_message('textDocument/didChange', {
|
||||
'textDocument': { 'uri': FILE_URI },
|
||||
'contentChanges': [
|
||||
{
|
||||
'range': {
|
||||
'start': { 'line': 6, 'character': 2 },
|
||||
'end': { 'line': 6, 'character': 2 }
|
||||
},
|
||||
'text': 'unction f() public {}'
|
||||
}
|
||||
]
|
||||
})
|
||||
published_diagnostics = self.wait_for_diagnostics(solc, 1)
|
||||
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_didChange_empty_file(self, solc: JsonRpcProcess) -> None:
|
||||
"""
|
||||
Starts with an empty file and changes it to look like
|
||||
the didOpen_with_import test case. Then we can use
|
||||
the same verification calls to ensure it worked as expected.
|
||||
"""
|
||||
# This FILE_NAME must be alphabetically before lib.sol to not over-complify
|
||||
# the test logic in verify_didOpen_with_import_diagnostics.
|
||||
FILE_NAME = 'a_new_file'
|
||||
FILE_URI = self.get_test_file_uri(FILE_NAME)
|
||||
self.setup_lsp(solc)
|
||||
solc.send_message('textDocument/didOpen', {
|
||||
'textDocument': {
|
||||
'uri': FILE_URI,
|
||||
'languageId': 'Solidity',
|
||||
'version': 1,
|
||||
'text': ''
|
||||
}
|
||||
})
|
||||
reports = self.wait_for_diagnostics(solc, 1)
|
||||
self.expect_equal(len(reports), 1)
|
||||
report = reports[0]
|
||||
published_diagnostics = report['diagnostics']
|
||||
self.expect_equal(len(published_diagnostics), 2)
|
||||
self.expect_diagnostic(published_diagnostics[0], code=1878, lineNo=0, startEndColumns=(0, 0))
|
||||
self.expect_diagnostic(published_diagnostics[1], code=3420, lineNo=0, startEndColumns=(0, 0))
|
||||
solc.send_message('textDocument/didChange', {
|
||||
'textDocument': {
|
||||
'uri': self.get_test_file_uri('a_new_file')
|
||||
},
|
||||
'contentChanges': [
|
||||
{
|
||||
'range': {
|
||||
'start': { 'line': 0, 'character': 0 },
|
||||
'end': { 'line': 0, 'character': 0 }
|
||||
},
|
||||
'text': self.get_test_file_contents('didOpen_with_import')
|
||||
}
|
||||
]
|
||||
})
|
||||
published_diagnostics = self.wait_for_diagnostics(solc, 2)
|
||||
self.verify_didOpen_with_import_diagnostics(published_diagnostics, 'a_new_file')
|
||||
|
||||
def test_textDocument_didChange_multi_line(self, solc: JsonRpcProcess) -> None:
|
||||
"""
|
||||
Starts with an empty file and changes it to multiple times, changing
|
||||
content across lines.
|
||||
"""
|
||||
self.setup_lsp(solc)
|
||||
FILE_NAME = 'didChange_template'
|
||||
FILE_URI = self.get_test_file_uri(FILE_NAME)
|
||||
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, 1)
|
||||
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', {
|
||||
'textDocument': { 'uri': FILE_URI },
|
||||
'contentChanges': [
|
||||
{
|
||||
'range': {
|
||||
'start': { 'line': 3, 'character': 3 },
|
||||
'end': { 'line': 4, 'character': 1 }
|
||||
},
|
||||
'text': "tract D {\n\n uint x\n = -1; \n "
|
||||
}
|
||||
]
|
||||
})
|
||||
published_diagnostics = self.wait_for_diagnostics(solc, 1)
|
||||
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")
|
||||
self.expect_equal(len(report2['diagnostics']), 1, "one diagnostic")
|
||||
self.expect_diagnostic(report2['diagnostics'][0], 7407, 6, (3, 5))
|
||||
|
||||
# Now we are changing the part "x\n = -" of "uint x\n = -1;"
|
||||
solc.send_message('textDocument/didChange', {
|
||||
'textDocument': { 'uri': FILE_URI },
|
||||
'contentChanges': [
|
||||
{
|
||||
'range': {
|
||||
'start': { 'line': 5, 'character': 7 },
|
||||
'end': { 'line': 6, 'character': 4 }
|
||||
},
|
||||
'text': "y\n = [\nuint(1),\n3,4]+"
|
||||
}
|
||||
]
|
||||
})
|
||||
published_diagnostics = self.wait_for_diagnostics(solc, 1)
|
||||
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']), 2, "two diagnostics")
|
||||
diagnostic = report3['diagnostics'][0]
|
||||
self.expect_equal(diagnostic['code'], 2271, 'diagnostic: 2271')
|
||||
# check multi-line error code
|
||||
self.expect_equal(
|
||||
diagnostic['range'],
|
||||
{
|
||||
'end': {'character': 6, 'line': 8},
|
||||
'start': {'character': 3, 'line': 6}
|
||||
},
|
||||
"diagnostic: check range"
|
||||
)
|
||||
diagnostic = report3['diagnostics'][1]
|
||||
self.expect_equal(diagnostic['code'], 7407, 'diagnostic: 7407')
|
||||
# check multi-line error code
|
||||
self.expect_equal(
|
||||
diagnostic['range'],
|
||||
{
|
||||
'end': {'character': 6, 'line': 8},
|
||||
'start': {'character': 3, 'line': 6}
|
||||
},
|
||||
"diagnostic: check range"
|
||||
)
|
||||
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
if __name__ == "__main__":
|
||||
suite = SolidityLSPTestSuite()
|
||||
exit_code = suite.main()
|
||||
exit(exit_code)
|
@ -158,7 +158,7 @@ BOOST_AUTO_TEST_CASE(multiple_input_modes)
|
||||
};
|
||||
string expectedMessage =
|
||||
"The following options are mutually exclusive: "
|
||||
"--help, --license, --version, --standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast. "
|
||||
"--help, --license, --version, --standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast, --lsp. "
|
||||
"Select at most one.";
|
||||
|
||||
for (string const& mode1: inputModeOptions)
|
||||
|
Loading…
Reference in New Issue
Block a user