1 /*
2 	This file is part of solidity.
3 
4 	solidity is free software: you can redistribute it and/or modify
5 	it under the terms of the GNU General Public License as published by
6 	the Free Software Foundation, either version 3 of the License, or
7 	(at your option) any later version.
8 
9 	solidity is distributed in the hope that it will be useful,
10 	but WITHOUT ANY WARRANTY; without even the implied warranty of
11 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 	GNU General Public License for more details.
13 
14 	You should have received a copy of the GNU General Public License
15 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 // SPDX-License-Identifier: GPL-3.0
18 /**
19  * @author Christian <c@ethdev.com>
20  * @date 2014
21  * Tests for the Solidity optimizer.
22  */
23 
24 #include <test/Metadata.h>
25 #include <test/libsolidity/SolidityExecutionFramework.h>
26 
27 #include <libevmasm/Instruction.h>
28 
29 #include <boost/test/unit_test.hpp>
30 
31 #include <chrono>
32 #include <string>
33 #include <tuple>
34 #include <memory>
35 #include <limits>
36 
37 using namespace std;
38 using namespace solidity::util;
39 using namespace solidity::evmasm;
40 using namespace solidity::test;
41 
42 namespace solidity::frontend::test
43 {
44 
45 class OptimizerTestFramework: public SolidityExecutionFramework
46 {
47 public:
compileAndRunWithOptimizer(std::string const & _sourceCode,u256 const & _value=0,std::string const & _contractName="",bool const _optimize=true,unsigned const _optimizeRuns=200)48 	bytes const& compileAndRunWithOptimizer(
49 		std::string const& _sourceCode,
50 		u256 const& _value = 0,
51 		std::string const& _contractName = "",
52 		bool const _optimize = true,
53 		unsigned const _optimizeRuns = 200
54 	)
55 	{
56 		OptimiserSettings previousSettings = std::move(m_optimiserSettings);
57 		// This uses "none" / "full" while most other test frameworks use
58 		// "minimal" / "standard".
59 		m_optimiserSettings = _optimize ? OptimiserSettings::full() : OptimiserSettings::none();
60 		m_optimiserSettings.expectedExecutionsPerDeployment = _optimizeRuns;
61 		bytes const& ret = compileAndRun(_sourceCode, _value, _contractName);
62 		m_optimiserSettings = std::move(previousSettings);
63 		return ret;
64 	}
65 
66 	/// Compiles the source code with and without optimizing.
compileBothVersions(std::string const & _sourceCode,u256 const & _value=0,std::string const & _contractName="",unsigned const _optimizeRuns=200)67 	void compileBothVersions(
68 		std::string const& _sourceCode,
69 		u256 const& _value = 0,
70 		std::string const& _contractName = "",
71 		unsigned const _optimizeRuns = 200
72 	)
73 	{
74 		m_nonOptimizedBytecode = compileAndRunWithOptimizer("pragma solidity >=0.0;\n" + _sourceCode, _value, _contractName, false, _optimizeRuns);
75 		m_nonOptimizedContract = m_contractAddress;
76 		m_optimizedBytecode = compileAndRunWithOptimizer("pragma solidity >=0.0;\n" + _sourceCode, _value, _contractName, true, _optimizeRuns);
77 		size_t nonOptimizedSize = numInstructions(m_nonOptimizedBytecode);
78 		size_t optimizedSize = numInstructions(m_optimizedBytecode);
79 		BOOST_CHECK_MESSAGE(
80 			_optimizeRuns < 50 || optimizedSize < nonOptimizedSize,
81 			string("Optimizer did not reduce bytecode size. Non-optimized size: ") +
82 			to_string(nonOptimizedSize) + " - optimized size: " +
83 			to_string(optimizedSize)
84 		);
85 		m_optimizedContract = m_contractAddress;
86 	}
87 
88 	template <class... Args>
compareVersions(std::string _sig,Args const &..._arguments)89 	void compareVersions(std::string _sig, Args const&... _arguments)
90 	{
91 		m_contractAddress = m_nonOptimizedContract;
92 		bytes nonOptimizedOutput = callContractFunction(_sig, _arguments...);
93 		m_gasUsedNonOptimized = m_gasUsed;
94 		m_contractAddress = m_optimizedContract;
95 		bytes optimizedOutput = callContractFunction(_sig, _arguments...);
96 		m_gasUsedOptimized = m_gasUsed;
97 		BOOST_CHECK_MESSAGE(!optimizedOutput.empty(), "No optimized output for " + _sig);
98 		BOOST_CHECK_MESSAGE(!nonOptimizedOutput.empty(), "No un-optimized output for " + _sig);
99 		BOOST_CHECK_MESSAGE(nonOptimizedOutput == optimizedOutput, "Computed values do not match."
100 							"\nNon-Optimized: " + toHex(nonOptimizedOutput) +
101 							"\nOptimized:     " + toHex(optimizedOutput));
102 	}
103 
104 	/// @returns the number of instructions in the given bytecode, not taking the metadata hash
105 	/// into account.
numInstructions(bytes const & _bytecode,std::optional<Instruction> _which=std::optional<Instruction>{})106 	size_t numInstructions(bytes const& _bytecode, std::optional<Instruction> _which = std::optional<Instruction>{})
107 	{
108 		bytes realCode = bytecodeSansMetadata(_bytecode);
109 		BOOST_REQUIRE_MESSAGE(!realCode.empty(), "Invalid or missing metadata in bytecode.");
110 		size_t instructions = 0;
__anon2037d66b0102(Instruction _instr, u256 const&) 111 		evmasm::eachInstruction(realCode, [&](Instruction _instr, u256 const&) {
112 			if (!_which || *_which == _instr)
113 				instructions++;
114 		});
115 		return instructions;
116 	}
117 
118 protected:
119 	u256 m_gasUsedOptimized;
120 	u256 m_gasUsedNonOptimized;
121 	bytes m_nonOptimizedBytecode;
122 	bytes m_optimizedBytecode;
123 	h160 m_optimizedContract;
124 	h160 m_nonOptimizedContract;
125 };
126 
BOOST_FIXTURE_TEST_SUITE(SolidityOptimizer,OptimizerTestFramework)127 BOOST_FIXTURE_TEST_SUITE(SolidityOptimizer, OptimizerTestFramework)
128 
129 BOOST_AUTO_TEST_CASE(smoke_test)
130 {
131 	char const* sourceCode = R"(
132 		contract test {
133 			function f(uint a) public returns (uint b) {
134 				return a;
135 			}
136 		}
137 	)";
138 	compileBothVersions(sourceCode);
139 	compareVersions("f(uint256)", u256(7));
140 }
141 
BOOST_AUTO_TEST_CASE(identities)142 BOOST_AUTO_TEST_CASE(identities)
143 {
144 	char const* sourceCode = R"(
145 		contract test {
146 			function f(int a) public returns (int b) {
147 				return int(0) | (int(1) * (int(0) ^ (0 + a)));
148 			}
149 		}
150 	)";
151 	compileBothVersions(sourceCode);
152 	compareVersions("f(int256)", u256(0x12334664));
153 }
154 
BOOST_AUTO_TEST_CASE(unused_expressions)155 BOOST_AUTO_TEST_CASE(unused_expressions)
156 {
157 	char const* sourceCode = R"(
158 		contract test {
159 			uint data;
160 			function f() public returns (uint a, uint b) {
161 				10 + 20;
162 				data;
163 			}
164 		}
165 	)";
166 	compileBothVersions(sourceCode);
167 	compareVersions("f()");
168 }
169 
BOOST_AUTO_TEST_CASE(constant_folding_both_sides)170 BOOST_AUTO_TEST_CASE(constant_folding_both_sides)
171 {
172 	// if constants involving the same associative and commutative operator are applied from both
173 	// sides, the operator should be applied only once, because the expression compiler pushes
174 	// literals as late as possible
175 	char const* sourceCode = R"(
176 		contract test {
177 			function f(uint x) public returns (uint y) {
178 				return 98 ^ (7 * ((1 | (x | 1000)) * 40) ^ 102);
179 			}
180 		}
181 	)";
182 	compileBothVersions(sourceCode);
183 	compareVersions("f(uint256)", 7);
184 }
185 
BOOST_AUTO_TEST_CASE(storage_access)186 BOOST_AUTO_TEST_CASE(storage_access)
187 {
188 	char const* sourceCode = R"(
189 		contract test {
190 			uint8[40] data;
191 			function f(uint x) public returns (uint y) {
192 				data[2] = data[7] = uint8(x);
193 				data[4] = data[2] * 10 + data[3];
194 			}
195 		}
196 	)";
197 	compileBothVersions(sourceCode);
198 	compareVersions("f(uint256)", 7);
199 }
200 
BOOST_AUTO_TEST_CASE(array_copy)201 BOOST_AUTO_TEST_CASE(array_copy)
202 {
203 	char const* sourceCode = R"(
204 		contract test {
205 			bytes2[] data1;
206 			bytes5[] data2;
207 			function f(uint x) public returns (uint l, uint y) {
208 				for (uint i = 0; i < msg.data.length; i++)
209 					data1.push();
210 				for (uint i = 0; i < msg.data.length; ++i)
211 					data1[i] = msg.data[i];
212 				data2 = data1;
213 				l = data2.length;
214 				y = uint(uint40(data2[x]));
215 			}
216 		}
217 	)";
218 	compileBothVersions(sourceCode);
219 	compareVersions("f(uint256)", 0);
220 	compareVersions("f(uint256)", 10);
221 	compareVersions("f(uint256)", 35);
222 }
223 
BOOST_AUTO_TEST_CASE(function_calls)224 BOOST_AUTO_TEST_CASE(function_calls)
225 {
226 	char const* sourceCode = R"(
227 		contract test {
228 			function f1(uint x) public returns (uint) { unchecked { return x*x; } }
229 			function f(uint x) public returns (uint) { unchecked { return f1(7+x) - this.f1(x**9); } }
230 		}
231 	)";
232 	compileBothVersions(sourceCode);
233 	compareVersions("f(uint256)", 0);
234 	compareVersions("f(uint256)", 10);
235 	compareVersions("f(uint256)", 36);
236 }
237 
BOOST_AUTO_TEST_CASE(storage_write_in_loops)238 BOOST_AUTO_TEST_CASE(storage_write_in_loops)
239 {
240 	char const* sourceCode = R"(
241 		contract test {
242 			uint d;
243 			function f(uint a) public returns (uint r) {
244 				uint x = d;
245 				for (uint i = 1; i < a * a; i++) {
246 					r = d;
247 					d = i;
248 				}
249 
250 			}
251 		}
252 	)";
253 	compileBothVersions(sourceCode);
254 	compareVersions("f(uint256)", 0);
255 	compareVersions("f(uint256)", 10);
256 	compareVersions("f(uint256)", 36);
257 }
258 
259 // Test disabled with https://github.com/ethereum/solidity/pull/762
260 // Information in joining branches is not retained anymore.
BOOST_AUTO_TEST_CASE(retain_information_in_branches)261 BOOST_AUTO_TEST_CASE(retain_information_in_branches)
262 {
263 	// This tests that the optimizer knows that we already have "z == keccak256(abi.encodePacked(y))" inside both branches.
264 	char const* sourceCode = R"(
265 		contract c {
266 			bytes32 d;
267 			uint a;
268 			function f(uint x, bytes32 y) public returns (uint r_a, bytes32 r_d) {
269 				bytes32 z = keccak256(abi.encodePacked(y));
270 				if (x > 8) {
271 					z = keccak256(abi.encodePacked(y));
272 					a = x;
273 				} else {
274 					z = keccak256(abi.encodePacked(y));
275 					a = x;
276 				}
277 				r_a = a;
278 				r_d = d;
279 			}
280 		}
281 	)";
282 	compileBothVersions(sourceCode);
283 	compareVersions("f(uint256,bytes32)", 0, "abc");
284 	compareVersions("f(uint256,bytes32)", 8, "def");
285 	compareVersions("f(uint256,bytes32)", 10, "ghi");
286 
287 	bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "c", true);
288 	size_t numSHA3s = 0;
289 	eachInstruction(optimizedBytecode, [&](Instruction _instr, u256 const&) {
290 		if (_instr == Instruction::KECCAK256)
291 			numSHA3s++;
292 	});
293 // TEST DISABLED - OPTIMIZER IS NOT EFFECTIVE ON THIS ONE ANYMORE
294 //	BOOST_CHECK_EQUAL(1, numSHA3s);
295 }
296 
BOOST_AUTO_TEST_CASE(store_tags_as_unions)297 BOOST_AUTO_TEST_CASE(store_tags_as_unions)
298 {
299 	// This calls the same function from two sources and both calls have a certain Keccak-256 on
300 	// the stack at the same position.
301 	// Without storing tags as unions, the return from the shared function would not know where to
302 	// jump and thus all jumpdests are forced to clear their state and we do not know about the
303 	// sha3 anymore.
304 	// Note that, for now, this only works if the functions have the same number of return
305 	// parameters since otherwise, the return jump addresses are at different stack positions
306 	// which triggers the "unknown jump target" situation.
307 	char const* sourceCode = R"(
308 		contract test {
309 			bytes32 data;
310 			function f(uint x, bytes32 y) external returns (uint r_a, bytes32 r_d) {
311 				r_d = keccak256(abi.encodePacked(y));
312 				shared(y);
313 				r_d = keccak256(abi.encodePacked(y));
314 				r_a = 5;
315 			}
316 			function g(uint x, bytes32 y) external returns (uint r_a, bytes32 r_d) {
317 				r_d = keccak256(abi.encodePacked(y));
318 				shared(y);
319 				r_d = bytes32(uint(keccak256(abi.encodePacked(y))) + 2);
320 				r_a = 7;
321 			}
322 			function shared(bytes32 y) internal {
323 				data = keccak256(abi.encodePacked(y));
324 			}
325 		}
326 	)";
327 	compileBothVersions(sourceCode);
328 	compareVersions("f(uint256,bytes32)", 7, "abc");
329 
330 	bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "test", true);
331 	size_t numSHA3s = 0;
332 	eachInstruction(optimizedBytecode, [&](Instruction _instr, u256 const&) {
333 		if (_instr == Instruction::KECCAK256)
334 			numSHA3s++;
335 	});
336 // TEST DISABLED UNTIL 93693404 IS IMPLEMENTED
337 //	BOOST_CHECK_EQUAL(2, numSHA3s);
338 }
339 
BOOST_AUTO_TEST_CASE(incorrect_storage_access_bug)340 BOOST_AUTO_TEST_CASE(incorrect_storage_access_bug)
341 {
342 	// This bug appeared because a Keccak-256 operation with too low sequence number was used,
343 	// resulting in memory not being rewritten before the Keccak-256. The fix was to
344 	// take the max of the min sequence numbers when merging the states.
345 	char const* sourceCode = R"(
346 		contract C
347 		{
348 			mapping(uint => uint) data;
349 			function f() public returns (uint)
350 			{
351 				if (data[block.timestamp] == 0)
352 					data[type(uint).max - 6] = 5;
353 				return data[block.timestamp];
354 			}
355 		}
356 	)";
357 	compileBothVersions(sourceCode);
358 	compareVersions("f()");
359 }
360 
BOOST_AUTO_TEST_CASE(sequence_number_for_calls)361 BOOST_AUTO_TEST_CASE(sequence_number_for_calls)
362 {
363 	// This is a test for a bug that was present because we did not increment the sequence
364 	// number for CALLs - CALLs can read and write from memory (and DELEGATECALLs can do the same
365 	// to storage), so the sequence number should be incremented.
366 	char const* sourceCode = R"(
367 		contract test {
368 			function f(string memory a, string memory b) public returns (bool) { return sha256(bytes(a)) == sha256(bytes(b)); }
369 		}
370 	)";
371 	compileBothVersions(sourceCode);
372 	compareVersions("f(string,string)", 0x40, 0x80, 3, "abc", 3, "def");
373 }
374 
BOOST_AUTO_TEST_CASE(computing_constants)375 BOOST_AUTO_TEST_CASE(computing_constants)
376 {
377 	char const* sourceCode = R"(
378 		contract C {
379 			uint m_a;
380 			uint m_b;
381 			uint m_c;
382 			uint m_d;
383 			constructor() {
384 				set();
385 			}
386 			function set() public returns (uint) {
387 				m_a = 0x77abc0000000000000000000000000000000000000000000000000000000001;
388 				m_b = 0x817416927846239487123469187231298734162934871263941234127518276;
389 				g();
390 				return 1;
391 			}
392 			function g() public {
393 				m_b = 0x817416927846239487123469187231298734162934871263941234127518276;
394 				m_c = 0x817416927846239487123469187231298734162934871263941234127518276;
395 				h();
396 			}
397 			function h() public {
398 				m_d = 0xff05694900000000000000000000000000000000000000000000000000000000;
399 			}
400 			function get() public returns (uint ra, uint rb, uint rc, uint rd) {
401 				ra = m_a;
402 				rb = m_b;
403 				rc = m_c;
404 				rd = m_d;
405 			}
406 		}
407 	)";
408 	compileBothVersions(sourceCode, 0, "C", 1);
409 	compareVersions("get()");
410 	compareVersions("set()");
411 	compareVersions("get()");
412 
413 	bytes optimizedBytecode = compileAndRunWithOptimizer(sourceCode, 0, "C", true, 1);
414 	bytes complicatedConstant = toBigEndian(u256("0x817416927846239487123469187231298734162934871263941234127518276"));
415 	unsigned occurrences = 0;
416 	for (auto iter = optimizedBytecode.cbegin(); iter < optimizedBytecode.cend(); ++occurrences)
417 	{
418 		iter = search(iter, optimizedBytecode.cend(), complicatedConstant.cbegin(), complicatedConstant.cend());
419 		if (iter < optimizedBytecode.cend())
420 			++iter;
421 	}
422 	BOOST_CHECK_EQUAL(2, occurrences);
423 
424 	bytes constantWithZeros = toBigEndian(u256("0x77abc0000000000000000000000000000000000000000000000000000000001"));
425 	BOOST_CHECK(search(
426 		optimizedBytecode.cbegin(),
427 		optimizedBytecode.cend(),
428 		constantWithZeros.cbegin(),
429 		constantWithZeros.cend()
430 	) == optimizedBytecode.cend());
431 }
432 
433 
BOOST_AUTO_TEST_CASE(constant_optimization_early_exit)434 BOOST_AUTO_TEST_CASE(constant_optimization_early_exit)
435 {
436 	// This tests that the constant optimizer does not try to find the best representation
437 	// indefinitely but instead stops after some number of iterations.
438 	char const* sourceCode = R"(
439 	contract HexEncoding {
440 		function hexEncodeTest(address addr) public returns (bytes32 ret) {
441 			uint x = uint(uint160(addr)) / 2**32;
442 
443 			// Nibble interleave
444 			x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
445 			x = (x | (x * 2**64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff;
446 			x = (x | (x * 2**32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff;
447 			x = (x | (x * 2**16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff;
448 			x = (x | (x * 2** 8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff;
449 			x = (x | (x * 2** 4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;
450 
451 			// Hex encode
452 			uint h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8;
453 			uint i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4;
454 			uint j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2;
455 			x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030;
456 
457 			// Store and load next batch
458 			assembly {
459 				mstore(0, x)
460 			}
461 			x = uint160(addr) * 2**96;
462 
463 			// Nibble interleave
464 			x = x & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
465 			x = (x | (x * 2**64)) & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff;
466 			x = (x | (x * 2**32)) & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff;
467 			x = (x | (x * 2**16)) & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff;
468 			x = (x | (x * 2** 8)) & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff;
469 			x = (x | (x * 2** 4)) & 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;
470 
471 			// Hex encode
472 			h = (x & 0x0808080808080808080808080808080808080808080808080808080808080808) / 8;
473 			i = (x & 0x0404040404040404040404040404040404040404040404040404040404040404) / 4;
474 			j = (x & 0x0202020202020202020202020202020202020202020202020202020202020202) / 2;
475 			x = x + (h & (i | j)) * 0x27 + 0x3030303030303030303030303030303030303030303030303030303030303030;
476 
477 			// Store and hash
478 			assembly {
479 				mstore(32, x)
480 				ret := keccak256(0, 40)
481 			}
482 		}
483 	}
484 	)";
485 	auto start = std::chrono::steady_clock::now();
486 	compileBothVersions(sourceCode);
487 	double duration = std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
488 	// Since run time on an ASan build is not really realistic, we disable this test for those builds.
489 	size_t maxDuration = 20;
490 #if !defined(__SANITIZE_ADDRESS__) && defined(__has_feature)
491 #if __has_feature(address_sanitizer)
492 #define __SANITIZE_ADDRESS__ 1
493 #endif
494 #endif
495 #if __SANITIZE_ADDRESS__
496 	maxDuration = numeric_limits<size_t>::max();
497 	BOOST_TEST_MESSAGE("Disabled constant optimizer run time check for address sanitizer build.");
498 #endif
499 	BOOST_CHECK_MESSAGE(duration <= double(maxDuration), "Compilation of constants took longer than 20 seconds.");
500 	compareVersions("hexEncodeTest(address)", u256(0x123456789));
501 }
502 
BOOST_AUTO_TEST_CASE(inconsistency)503 BOOST_AUTO_TEST_CASE(inconsistency)
504 {
505 	// This is a test of a bug in the optimizer.
506 	char const* sourceCode = R"(
507 		contract Inconsistency {
508 			struct Value {
509 				uint badnum;
510 				uint number;
511 			}
512 
513 			struct Container {
514 				uint[] valueIndices;
515 				Value[] values;
516 			}
517 
518 			Container[] containers;
519 			uint[] valueIndices;
520 			uint INDEX_ZERO = 0;
521 			uint  debug;
522 
523 			// Called with params: containerIndex=0, valueIndex=0
524 			function levelIII(uint containerIndex, uint valueIndex) private {
525 				Container storage container = containers[containerIndex];
526 				Value storage value = container.values[valueIndex];
527 				debug = container.valueIndices[value.number];
528 			}
529 			function levelII() private {
530 				for (uint i = 0; i < valueIndices.length; i++) {
531 					levelIII(INDEX_ZERO, valueIndices[i]);
532 				}
533 			}
534 
535 			function trigger() public returns (uint) {
536 				Container storage container = containers.push();
537 
538 				container.values.push(Value({
539 					badnum: 9000,
540 					number: 0
541 				}));
542 
543 				container.valueIndices.push();
544 				valueIndices.push();
545 
546 				levelII();
547 				return debug;
548 			}
549 
550 			function DoNotCallButDoNotDelete() public {
551 				levelII();
552 				levelIII(1, 2);
553 			}
554 		}
555 	)";
556 	compileBothVersions(sourceCode);
557 	compareVersions("trigger()");
558 }
559 
BOOST_AUTO_TEST_CASE(dead_code_elimination_across_assemblies)560 BOOST_AUTO_TEST_CASE(dead_code_elimination_across_assemblies)
561 {
562 	// This tests that a runtime-function that is stored in storage in the constructor
563 	// is not removed as part of dead code elimination.
564 	char const* sourceCode = R"(
565 		contract DCE {
566 			function () internal returns (uint) stored;
567 			constructor() {
568 				stored = f;
569 			}
570 			function f() internal returns (uint) { return 7; }
571 			function test() public returns (uint) { return stored(); }
572 		}
573 	)";
574 	compileBothVersions(sourceCode);
575 	compareVersions("test()");
576 }
577 
BOOST_AUTO_TEST_CASE(invalid_state_at_control_flow_join)578 BOOST_AUTO_TEST_CASE(invalid_state_at_control_flow_join)
579 {
580 	char const* sourceCode = R"(
581 		contract Test {
582 			uint256 public totalSupply = 100;
583 			function f() public returns (uint r) {
584 				if (false)
585 					r = totalSupply;
586 				totalSupply -= 10;
587 			}
588 			function test() public returns (uint) {
589 				f();
590 				return this.totalSupply();
591 			}
592 		}
593 	)";
594 	compileBothVersions(sourceCode);
595 	compareVersions("test()");
596 }
597 
BOOST_AUTO_TEST_CASE(init_empty_dynamic_arrays)598 BOOST_AUTO_TEST_CASE(init_empty_dynamic_arrays)
599 {
600 	// This is not so much an optimizer test, but rather a test
601 	// that allocating empty arrays is implemented efficiently.
602 	// In particular, initializing a dynamic memory array does
603 	// not use any memory.
604 	char const* sourceCode = R"(
605 		contract Test {
606 			function f() public pure returns (uint r) {
607 				uint[][] memory x = new uint[][](20000);
608 				return x.length;
609 			}
610 		}
611 	)";
612 	compileBothVersions(sourceCode);
613 	compareVersions("f()");
614 	BOOST_CHECK_LE(m_gasUsedNonOptimized, 1900000);
615 	BOOST_CHECK_LE(1600000, m_gasUsedNonOptimized);
616 }
617 
BOOST_AUTO_TEST_CASE(optimise_multi_stores)618 BOOST_AUTO_TEST_CASE(optimise_multi_stores)
619 {
620 	char const* sourceCode = R"(
621 		contract Test {
622 			struct S { uint16 a; uint16 b; uint16[3] c; uint[] dyn; }
623 			uint padding;
624 			S[] s;
625 			function f() public returns (uint16, uint16, uint16[3] memory, uint) {
626 				uint16[3] memory c;
627 				c[0] = 7;
628 				c[1] = 8;
629 				c[2] = 9;
630 				s.push(S(1, 2, c, new uint[](4)));
631 				return (s[0].a, s[0].b, s[0].c, s[0].dyn[2]);
632 			}
633 		}
634 	)";
635 	compileBothVersions(sourceCode);
636 	compareVersions("f()");
637 	BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::SSTORE), 8);
638 	BOOST_CHECK_EQUAL(numInstructions(m_optimizedBytecode, Instruction::SSTORE), 7);
639 }
640 
BOOST_AUTO_TEST_CASE(optimise_constant_to_codecopy)641 BOOST_AUTO_TEST_CASE(optimise_constant_to_codecopy)
642 {
643 	char const* sourceCode = R"(
644 		contract C {
645 			// We use the state variable so that the functions won't be deemed identical
646 			// and be optimised out to the same implementation.
647 			uint a;
648 			function f() public returns (uint) {
649 				a = 1;
650 				// This cannot be represented well with the `CalculateMethod`,
651 				// hence the decision will be between `LiteralMethod` and `CopyMethod`.
652 				return 0x1234123412431234123412412342112341234124312341234124;
653 			}
654 			function g() public returns (uint) {
655 				a = 2;
656 				return 0x1234123412431234123412412342112341234124312341234124;
657 			}
658 			function h() public returns (uint) {
659 				a = 3;
660 				return 0x1234123412431234123412412342112341234124312341234124;
661 			}
662 			function i() public returns (uint) {
663 				a = 4;
664 				return 0x1234123412431234123412412342112341234124312341234124;
665 			}
666 		}
667 	)";
668 	compileBothVersions(sourceCode, 0, "C", 50);
669 	compareVersions("f()");
670 	compareVersions("g()");
671 	compareVersions("h()");
672 	compareVersions("i()");
673 	// This is counting in the deployed code.
674 	BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::CODECOPY), 0);
675 	BOOST_CHECK_EQUAL(numInstructions(m_optimizedBytecode, Instruction::CODECOPY), 4);
676 }
677 
BOOST_AUTO_TEST_CASE(byte_access)678 BOOST_AUTO_TEST_CASE(byte_access)
679 {
680 	char const* sourceCode = R"(
681 		contract C
682 		{
683 			function f(bytes32 x) public returns (bytes1 r)
684 			{
685 				assembly { r := and(byte(x, 31), 0xff) }
686 			}
687 		}
688 	)";
689 	compileBothVersions(sourceCode);
690 	compareVersions("f(bytes32)", u256("0x1223344556677889900112233445566778899001122334455667788990011223"));
691 }
692 
BOOST_AUTO_TEST_CASE(shift_optimizer_bug)693 BOOST_AUTO_TEST_CASE(shift_optimizer_bug)
694 {
695 	char const* sourceCode = R"(
696 		contract C
697 		{
698 			function f(uint x) public returns (uint)
699 			{
700 				return (x << 1) << type(uint).max;
701 			}
702 			function g(uint x) public returns (uint)
703 			{
704 				return (x >> 1) >> type(uint).max;
705 			}
706 		}
707 	)";
708 	compileBothVersions(sourceCode);
709 	compareVersions("f(uint256)", 7);
710 	compareVersions("g(uint256)", u256(-1));
711 }
712 
BOOST_AUTO_TEST_CASE(avoid_double_cleanup)713 BOOST_AUTO_TEST_CASE(avoid_double_cleanup)
714 {
715 	char const* sourceCode = R"(
716 		contract C {
717 			receive() external payable {
718 				abi.encodePacked(uint200(0));
719 			}
720 		}
721 	)";
722 	compileBothVersions(sourceCode, 0, "C", 50);
723 	// Check that there is no double AND instruction in the resulting code
724 	BOOST_CHECK_EQUAL(numInstructions(m_nonOptimizedBytecode, Instruction::AND), 1);
725 }
726 
727 BOOST_AUTO_TEST_SUITE_END()
728 
729 } // end namespaces
730