Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change translation of implicit throws #1598

Merged
merged 9 commits into from
Jan 27, 2017
Merged

Change translation of implicit throws #1598

merged 9 commits into from
Jan 27, 2017

Conversation

wuestholz
Copy link
Contributor

@wuestholz wuestholz commented Jan 22, 2017

Fixes #1589.

DO NOT MERGE, depensd on ethereum/EIPs#141

@chriseth @pirapira This adds a new invalid instruction that is used for encoding implicit throws that are emitted by the compiler. This makes it possible to distinguish such runtime errors from user-provided, explicit throws.

For some reason there seem to be three failing tests: SolidityEndToEndTest/function_delete_stack, SolidityEndToEndTest/call_function_returning_function, and SolidityEndToEndTest/conditional_expression_functions. The failures are due to a failing assertion in CompilerUtils::moveToStackVariable. However, it's not really clear to me why these changes should affect that assertion. It would be great if someone could have a look.


CompilerContext& CompilerContext::appendConditionalInvalid()
{
eth::AssemblyItem falseTag = appendConditionalJump();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wounder whether it might be better to just have a single jump:
ISZERO <continue> JUMPI INVALID continue:

Your code does:
<error> JUMPI <continue> JUMP error: INVALID continue:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intentionally tried not to be too clever about encoding the jumps. I had considered the same optimization. I have pushed this now.

@@ -910,7 +910,9 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime()
a << Instruction::DELEGATECALL;
//Propagate error condition (if DELEGATECALL pushes 0 on stack).
a << Instruction::ISZERO;
a.appendJumpI(a.errorTag());
eth::AssemblyItem falseTag = a.appendJumpI();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you use appendConditionalInvalid() here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not available for class Assembly and I didn't want to add it since it is only used here. However, I'd be happy to add it if you want.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, I'm sorry, I didn't see the full context. Is this case this is fine.

@chriseth
Copy link
Contributor

Thanks! Please don't forget to change the documentation, too.

@wuestholz
Copy link
Contributor Author

@chriseth Thank you very much for the feedback. I pushed changes that should address it.

Did you have chance to look into why those three tests might be failing now? It it possible that my changes somehow uncovered a separate issue?

{
*this << Instruction::ISZERO;
eth::AssemblyItem afterTag = appendConditionalJump();
return *this << Instruction::INVALID << afterTag;
Copy link
Contributor

@chriseth chriseth Jan 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code generator is not too smart about the stack height with regards to control flow, so you have to manually adjust the stack height at the end of this function (it assumes that control flow continues after invalid). This is the reason for the test failure. You can try this snippet to check whether it worked:

		contract test {
			function x() returns (uint) { return 1; }
			function y() returns (uint) { return 2; }

			function f(bool cond) returns (uint) {
				var z = cond ? x : y;
				return z();
			}
		}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I tried the following:

	unsigned startStackHeight = stackHeight();
	*this << Instruction::ISZERO;
	eth::AssemblyItem afterTag = appendConditionalJump();
	*this << Instruction::INVALID << afterTag;
	setStackOffset(startStackHeight - 1);
	return *this;

However, this didn't seem to do the trick. Were you thinking of something else?

@chriseth
Copy link
Contributor

Can you allow me access to your pull request branch (this is a new github feature, I think)? Then I will make the required adjustments.

@wuestholz
Copy link
Contributor Author

@chriseth Thanks! That's fine with me. I believe I checked some box about allowing this when I created the PR. I have also added you as a collaborator just in case.

@axic axic changed the title Change translation of implicit throws (issue #1589). Change translation of implicit throws Jan 26, 2017
@@ -171,6 +171,8 @@ enum class Instruction: uint8_t
LOG3, ///< Makes a log entry; 3 topics.
LOG4, ///< Makes a log entry; 4 topics.

INVALID = 0xef, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update to 0xfe as per the final EIP141.

@chriseth
Copy link
Contributor

This is still not fixed, but I think we are getting closer.

@chriseth
Copy link
Contributor

Ok, found the problem: Zero-initialized variables of internal function type have to be a tag. I found a solution above that is not the best yet, will submit a new version shortly.

@chriseth
Copy link
Contributor

@axic / @pirapira please review.

@wuestholz
Copy link
Contributor Author

@chriseth Great! Thanks for looking into this!


Internally, Solidity performs an "invalid jump" when an exception is thrown and thus causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect.
Internally, Solidity performs an "invalid jump" when a user-provided exception is thrown. In contrast, it performs an invalid operation
(code ``0xfe``) if a runtime exception is encountered. In both cases, this causes
Copy link
Member

@axic axic Jan 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe "performs an invalid operation (0xfe instruction)" sounds a bit clearer?

@@ -918,7 +918,9 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime()
a << Instruction::DELEGATECALL;
//Propagate error condition (if DELEGATECALL pushes 0 on stack).
a << Instruction::ISZERO;
a.appendJumpI(a.errorTag());
a << Instruction::ISZERO;
eth::AssemblyItem afterTag = a.appendJumpI();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this have a .tag()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it didn't fail because we don't have a test for it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, clone binaries are an undocumented and also mostly useless feature.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created an issue for that (#1618) so we can track.

@axic
Copy link
Member

axic commented Jan 27, 2017

It is missing an entry in the changelog.

@chriseth
Copy link
Contributor

Updated.

@axic
Copy link
Member

axic commented Jan 27, 2017

Is the idea for catch to insert tags for the jumps? If so, using invalid for non-user exceptions is the right way.

@chriseth
Copy link
Contributor

@axic I don't think catch can really work in a consistent way (it has to roll back changes inside a single call), but if we do something like that, then throw will be changed from jump(nonExistingTag) to jump(mload(mload(0x80))) and catch will push an address onto the stack pointed to at address 0x80.

@chriseth chriseth merged commit b2c35fb into ethereum:develop Jan 27, 2017
@wuestholz wuestholz mentioned this pull request Feb 16, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants