Skip to content

Commit c180cc3

Browse files
committed
[isoltest] Add support for builtin functions.
1 parent dde6353 commit c180cc3

File tree

9 files changed

+193
-41
lines changed

9 files changed

+193
-41
lines changed

liblangutil/Common.h

+5
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ inline bool isIdentifierPart(char c)
4545
return isIdentifierStart(c) || isDecimalDigit(c);
4646
}
4747

48+
inline bool isIdentifierPartWithDot(char c)
49+
{
50+
return isIdentifierPart(c) || c == '.';
51+
}
52+
4853
inline int hexValue(char c)
4954
{
5055
if (c >= '0' && c <= '9')

test/libsolidity/SemanticTest.cpp

+60-9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414

1515
#include <test/libsolidity/SemanticTest.h>
16+
#include <test/libsolidity/util/BytesUtils.h>
1617
#include <libsolutil/Whiskers.h>
1718
#include <libyul/Exceptions.h>
1819
#include <test/Common.h>
@@ -47,6 +48,15 @@ SemanticTest::SemanticTest(string const& _filename, langutil::EVMVersion _evmVer
4748
m_lineOffset(m_reader.lineNumber()),
4849
m_enforceViaYul(enforceViaYul)
4950
{
51+
using namespace std::placeholders;
52+
m_builtins
53+
= {{"smoke",
54+
{
55+
{"test0()", std::bind(&SemanticTest::builtinSmokeTest, this, _1)},
56+
{"test1(uint256)", std::bind(&SemanticTest::builtinSmokeTest, this, _1)},
57+
{"test2(uint256,uint256)", std::bind(&SemanticTest::builtinSmokeTest, this, _1)},
58+
}}};
59+
5060
string choice = m_reader.stringSetting("compileViaYul", "default");
5161
if (choice == "also")
5262
{
@@ -197,6 +207,15 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
197207
bytes output;
198208
if (test.call().kind == FunctionCall::Kind::LowLevel)
199209
output = callLowLevel(test.call().arguments.rawBytes(), test.call().value.value);
210+
else if (test.call().kind == FunctionCall::Kind::Builtin)
211+
{
212+
std::vector<string> builtinPath;
213+
boost::split(builtinPath, test.call().signature, boost::is_any_of("."));
214+
assert(builtinPath.size() == 2);
215+
auto builtin = m_builtins[builtinPath.front()][builtinPath.back()];
216+
output = builtin(test.call());
217+
test.setFailure(output.empty());
218+
}
200219
else
201220
{
202221
soltestAssert(
@@ -212,14 +231,33 @@ TestCase::TestResult SemanticTest::runTest(ostream& _stream, string const& _line
212231
);
213232
}
214233

215-
bool outputMismatch = (output != test.call().expectations.rawBytes());
216-
// Pre byzantium, it was not possible to return failure data, so we disregard
217-
// output mismatch for those EVM versions.
218-
if (test.call().expectations.failure && !m_transactionSuccessful && !m_evmVersion.supportsReturndata())
219-
outputMismatch = false;
220-
if (m_transactionSuccessful != !test.call().expectations.failure || outputMismatch)
221-
success = false;
234+
bytes expectationOutput;
235+
if (test.call().expectations.builtin)
236+
{
237+
std::vector<string> builtinPath;
238+
boost::split(builtinPath, test.call().expectations.builtin->signature, boost::is_any_of("."));
239+
assert(builtinPath.size() == 2);
240+
auto builtin = m_builtins[builtinPath.front()][builtinPath.back()];
241+
expectationOutput = builtin(*test.call().expectations.builtin);
242+
}
243+
else
244+
expectationOutput = test.call().expectations.rawBytes();
222245

246+
bool outputMismatch = (output != expectationOutput);
247+
if (test.call().kind == FunctionCall::Kind::Builtin)
248+
{
249+
if (outputMismatch)
250+
success = false;
251+
}
252+
else
253+
{
254+
// Pre byzantium, it was not possible to return failure data, so we disregard
255+
// output mismatch for those EVM versions.
256+
if (test.call().expectations.failure && !m_transactionSuccessful && !m_evmVersion.supportsReturndata())
257+
outputMismatch = false;
258+
if (m_transactionSuccessful != !test.call().expectations.failure || outputMismatch)
259+
success = false;
260+
}
223261
test.setFailure(!m_transactionSuccessful);
224262
test.setRawBytes(std::move(output));
225263
test.setContractABI(m_compiler.contractABI(m_compiler.lastContractName()));
@@ -340,13 +378,26 @@ void SemanticTest::printUpdatedSettings(ostream& _stream, string const& _linePre
340378

341379
void SemanticTest::parseExpectations(istream& _stream)
342380
{
343-
TestFileParser parser{_stream};
381+
TestFileParser parser{_stream, &this->m_builtins};
344382
auto functionCalls = parser.parseFunctionCalls(m_lineOffset);
345383
std::move(functionCalls.begin(), functionCalls.end(), back_inserter(m_tests));
346384
}
347385

348-
bool SemanticTest::deploy(string const& _contractName, u256 const& _value, bytes const& _arguments, map<string, solidity::test::Address> const& _libraries)
386+
bool SemanticTest::deploy(
387+
string const& _contractName,
388+
u256 const& _value,
389+
bytes const& _arguments,
390+
map<string, solidity::test::Address> const& _libraries)
349391
{
350392
auto output = compileAndRunWithoutCheck(m_sources.sources, _value, _contractName, _arguments, _libraries);
351393
return !output.empty() && m_transactionSuccessful;
352394
}
395+
396+
bytes SemanticTest::builtinSmokeTest(FunctionCall const& call)
397+
{
398+
// This function is only used in test/libsolidity/semanticTests/builtins/smoke.sol.
399+
bytes result;
400+
for (size_t i = 0; i < call.arguments.parameters.size(); ++i)
401+
result += util::toBigEndian(u256{call.arguments.parameters.at(i).rawBytes});
402+
return result;
403+
}

test/libsolidity/SemanticTest.h

+5
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict
5858
/// Compiles and deploys currently held source.
5959
/// Returns true if deployment was successful, false otherwise.
6060
bool deploy(std::string const& _contractName, u256 const& _value, bytes const& _arguments, std::map<std::string, solidity::test::Address> const& _libraries = {});
61+
6162
private:
63+
// builtin functions
64+
bytes builtinSmokeTest(FunctionCall const& call);
65+
6266
TestResult runTest(std::ostream& _stream, std::string const& _linePrefix, bool _formatted, bool _compileViaYul, bool _compileToEwasm);
6367
SourceMap m_sources;
6468
std::size_t m_lineOffset;
@@ -70,6 +74,7 @@ class SemanticTest: public SolidityExecutionFramework, public EVMVersionRestrict
7074
bool m_runWithABIEncoderV1Only = false;
7175
bool m_allowNonExistingFunctions = false;
7276
bool m_compileViaYulCanBeSet = false;
77+
BuiltinFunctions m_builtins;
7378
};
7479

7580
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
contract ClientReceipt {
2+
}
3+
// ====
4+
// compileViaYul: also
5+
// ----
6+
// smoke.test0() ->
7+
// smoke.test1(uint256): 1 -> 1
8+
// smoke.test2(uint256,uint256): 2, 3 -> 2, 3

test/libsolidity/util/SoltestTypes.h

+13-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include <libsolutil/AnsiColorized.h>
1818
#include <libsolutil/CommonData.h>
1919

20+
#include <test/ExecutionFramework.h>
21+
2022
namespace solidity::frontend::test
2123
{
2224

@@ -174,6 +176,8 @@ struct Parameter
174176
};
175177
using ParameterList = std::vector<Parameter>;
176178

179+
struct FunctionCall;
180+
177181
/**
178182
* Represents the expected result of a function call after it has been executed. This may be a single
179183
* return value or a comma-separated list of return values. It also contains the detected input
@@ -193,6 +197,9 @@ struct FunctionCallExpectations
193197
/// A Comment that can be attached to the expectations,
194198
/// that is retained and can be displayed.
195199
std::string comment;
200+
/// An expectation can also be defined by a builtin function.
201+
std::shared_ptr<FunctionCall> builtin;
202+
196203
/// ABI encoded `bytes` of parsed expected return values. It is checked
197204
/// against the actual result of a function call when used in test framework.
198205
bytes rawBytes() const
@@ -286,12 +293,17 @@ struct FunctionCall
286293
/// Marks a library deployment call.
287294
Library,
288295
/// Check that the storage of the current contract is empty or non-empty.
289-
Storage
296+
Storage,
297+
/// Call to a builtin.
298+
Builtin
290299
};
291300
Kind kind = Kind::Regular;
292301
/// Marks this function call as "short-handed", meaning
293302
/// no `->` declared.
294303
bool omitsArrow = true;
295304
};
296305

306+
using BuiltinFunction = std::function<bytes(FunctionCall const&)>;
307+
using BuiltinFunctions = std::map<std::string, std::map<std::string, BuiltinFunction>>;
308+
297309
}

test/libsolidity/util/TestFileParser.cpp

+60-12
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include <boost/algorithm/string.hpp>
2828
#include <boost/algorithm/string/predicate.hpp>
29+
#include <boost/algorithm/string/split.hpp>
2930
#include <boost/throw_exception.hpp>
3031

3132
#include <fstream>
@@ -107,6 +108,13 @@ vector<solidity::frontend::test::FunctionCall> TestFileParser::parseFunctionCall
107108
if (lowLevelCall)
108109
call.kind = FunctionCall::Kind::LowLevel;
109110

111+
// Treat all calls to functions containing '.' as calls to builtin functions.
112+
if (call.signature.find('.') != std::string::npos)
113+
{
114+
checkBuiltinFunction(call.signature);
115+
call.kind = FunctionCall::Kind::Builtin;
116+
}
117+
110118
if (accept(Token::Comma, true))
111119
call.value = parseFunctionCallValue();
112120

@@ -158,6 +166,24 @@ vector<solidity::frontend::test::FunctionCall> TestFileParser::parseFunctionCall
158166
return calls;
159167
}
160168

169+
void TestFileParser::checkBuiltinFunction(std::string const& signature)
170+
{
171+
assert(m_builtins != nullptr);
172+
vector<string> builtinPath;
173+
boost::split(builtinPath, signature, boost::is_any_of("."));
174+
assert(builtinPath.size() == 2);
175+
176+
auto module = m_builtins->find(builtinPath.front());
177+
if (module == m_builtins->end())
178+
throw TestParserError("builtin module '" + builtinPath.front() + "' not found");
179+
180+
auto builtin = module->second.find(builtinPath.back());
181+
if (builtin == module->second.end())
182+
throw TestParserError(
183+
"builtin function '" + builtinPath.back() + "' not found in module '"
184+
+ builtinPath.front() + "'");
185+
}
186+
161187
bool TestFileParser::accept(Token _token, bool const _expect)
162188
{
163189
if (m_scanner.currentToken() != _token)
@@ -257,21 +283,43 @@ FunctionCallExpectations TestFileParser::parseFunctionCallExpectations()
257283
{
258284
FunctionCallExpectations expectations;
259285

260-
auto param = parseParameter();
261-
if (param.abiType.type == ABIType::None)
286+
if (accept(Token::Identifier, false))
262287
{
263-
expectations.failure = false;
264-
return expectations;
288+
string signature;
289+
FunctionCallArgs arguments;
290+
291+
signature = parseFunctionSignature().first;
292+
if (signature.find('.') == std::string::npos)
293+
throw TestParserError("Only builtins can be called as an expectation.");
294+
295+
checkBuiltinFunction(signature);
296+
297+
if (accept(Token::Colon, true))
298+
arguments = parseFunctionCallArguments();
299+
300+
expectations.builtin = std::make_unique<FunctionCall>();
301+
expectations.builtin->arguments = arguments;
302+
expectations.builtin->signature = signature;
303+
expectations.builtin->kind = FunctionCall::Kind::Builtin;
265304
}
266-
expectations.result.emplace_back(param);
305+
else
306+
{
307+
auto param = parseParameter();
308+
if (param.abiType.type == ABIType::None)
309+
{
310+
expectations.failure = false;
311+
return expectations;
312+
}
313+
expectations.result.emplace_back(param);
267314

268-
while (accept(Token::Comma, true))
269-
expectations.result.emplace_back(parseParameter());
315+
while (accept(Token::Comma, true))
316+
expectations.result.emplace_back(parseParameter());
270317

271-
/// We have always one virtual parameter in the parameter list.
272-
/// If its type is FAILURE, the expected result is also a REVERT etc.
273-
if (expectations.result.at(0).abiType.type != ABIType::Failure)
274-
expectations.failure = false;
318+
/// We have always one virtual parameter in the parameter list.
319+
/// If its type is FAILURE, the expected result is also a REVERT etc.
320+
if (expectations.result.at(0).abiType.type != ABIType::Failure)
321+
expectations.failure = false;
322+
}
275323
return expectations;
276324
}
277325

@@ -602,7 +650,7 @@ string TestFileParser::Scanner::scanIdentifierOrKeyword()
602650
{
603651
string identifier;
604652
identifier += current();
605-
while (langutil::isIdentifierPart(peek()))
653+
while (langutil::isIdentifierPartWithDot(peek()))
606654
{
607655
advance();
608656
identifier += current();

test/libsolidity/util/TestFileParser.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class TestFileParser
5252
public:
5353
/// Constructor that takes an input stream \param _stream to operate on
5454
/// and creates the internal scanner.
55-
TestFileParser(std::istream& _stream): m_scanner(_stream) {}
55+
explicit TestFileParser(std::istream& _stream, BuiltinFunctions* _builtins = nullptr): m_scanner(_stream), m_builtins(_builtins) {}
5656

5757
/// Parses function calls blockwise and returns a list of function calls found.
5858
/// Throws an exception if a function call cannot be parsed because of its
@@ -177,12 +177,18 @@ class TestFileParser
177177
/// Parses the current string literal.
178178
std::string parseString();
179179

180+
/// Checks whether a builtin function with the given signature exist.
181+
/// This function will throw an TestParserError exception, if not.
182+
void checkBuiltinFunction(std::string const& signature);
183+
180184
/// A scanner instance
181185
Scanner m_scanner;
182186

183187
/// The current line number. Incremented when Token::Newline (//) is found and
184188
/// used to enhance parser error messages.
185189
size_t m_lineNumber = 0;
190+
191+
BuiltinFunctions* m_builtins{nullptr};
186192
};
187193

188194
}

test/libsolidity/util/TestFunctionCall.cpp

+22-5
Original file line numberDiff line numberDiff line change
@@ -124,20 +124,37 @@ string TestFunctionCall::format(
124124

125125
/// Format either the expected output or the actual result output
126126
string result;
127+
auto& builtin = m_call.expectations.builtin;
127128
if (!_renderResult)
128129
{
129-
bool const isFailure = m_call.expectations.failure;
130-
result = isFailure ?
131-
formatFailure(_errorReporter, m_call, m_rawBytes, _renderResult, highlight) :
132-
formatRawParameters(m_call.expectations.result);
130+
if (builtin)
131+
{
132+
result = builtin->signature;
133+
if (!builtin->arguments.parameters.empty())
134+
result += ": ";
135+
result += formatRawParameters(builtin->arguments.parameters);
136+
}
137+
else
138+
{
139+
bool const isFailure = m_call.expectations.failure;
140+
result = isFailure ?
141+
formatFailure(_errorReporter, m_call, m_rawBytes, _renderResult, highlight) :
142+
formatRawParameters(m_call.expectations.result);
143+
}
133144
if (!result.empty())
134-
AnsiColorized(stream, highlight, {util::formatting::RED_BACKGROUND}) << ws << result;
145+
{
146+
AnsiColorized(stream, false, {util::formatting::RESET}) << ws;
147+
AnsiColorized(stream, highlight, {util::formatting::RED_BACKGROUND}) << result;
148+
}
135149
}
136150
else
137151
{
138152
if (m_calledNonExistingFunction)
139153
_errorReporter.warning("The function \"" + m_call.signature + "\" is not known to the compiler.");
140154

155+
if (builtin)
156+
_errorReporter.warning("The expectation \"" + builtin->signature + ": " + formatRawParameters(builtin->arguments.parameters) + "\" will be replaced with the actual value returned by the test.");
157+
141158
bytes output = m_rawBytes;
142159
bool const isFailure = m_failure;
143160
result = isFailure ?

0 commit comments

Comments
 (0)