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

Prevent compiler crashes on certain consume expressions #3650

Merged
merged 5 commits into from
Sep 15, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .release-notes/3650.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Graceful error handling on `consume` expressions
Copy link
Member

Choose a reason for hiding this comment

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

I think what is missing from these release notes is an that previously some of these things might have been compiler errors etc before. The fix ensures some things, but what does that mean for the end-user?

Something along the lines of "previously any of X would result in Y that has now been fixed" is an important part of the release notes messaging.

Copy link
Contributor Author

@kapilash kapilash Sep 14, 2020

Choose a reason for hiding this comment

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

Rephrased. Can you please check if this sounds better? also I removed one of the points. I notice that 0.38.0 already covered it. (That particular fix was only refactored this time around).


The fix ensures
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
The fix ensures
The fix ensures:

* `consume (consume variable).field` results in a graceful compilation error
* `consume f().field` results in similar compilation error as `consume f()`
* Allow `(consume obj).field` expressions
kapilash marked this conversation as resolved.
Show resolved Hide resolved
The following snippet
```pony
kapilash marked this conversation as resolved.
Show resolved Hide resolved
(consume f).b = recover Bar end
```
must be equivalent to
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
```
must be equivalent to

must be equivalent to


```pony
let x = consume f
x.b = recover Bar end
```

8 changes: 3 additions & 5 deletions src/libponyc/pass/refer.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,16 @@ static const char* generate_multi_dot_name(ast_t* ast, ast_t** def_found) {

default:
{
if (def == NULL)
return stringtab("");
Copy link
Contributor Author

@kapilash kapilash Sep 14, 2020

Choose a reason for hiding this comment

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

@jemc , this is one change needed in generate_multi_dot_name.
I believe this

  • will do no harm to working pony code: The new code was added in a place where it is throwing an assert. So no working code will get affected.

  • is in sync with the rest of the method: if the parent ast node has null data, we are supposed to return empty string. That is possible when we have expressions wrapped in braces.

pony_assert(0);
}
}

if(def_found != NULL)
{
*def_found = def;

kapilash marked this conversation as resolved.
Show resolved Hide resolved
if(def == NULL)
return stringtab("");
}
Expand Down Expand Up @@ -1166,10 +1168,6 @@ static bool refer_consume(pass_opt_t* opt, ast_t* ast)
"can't consume a let or embed field");
return false;
}
} else if (ast_id(left) == TK_CALL) {
ast_error(opt->check.errors, ast,
"consume expressions must specify a single identifier");
return false;
}
else
{
Expand Down
11 changes: 8 additions & 3 deletions src/libponyc/pass/syntax.c
Original file line number Diff line number Diff line change
Expand Up @@ -696,14 +696,19 @@ static ast_result_t syntax_consume(pass_opt_t* opt, ast_t* ast)
{
case TK_THIS:
case TK_REFERENCE:
case TK_DOT:
return AST_OK;

case TK_DOT: {
AST_GET_CHILDREN(term, left, right);
if (ast_id(left) != TK_CALL && ast_id(left) != TK_SEQ)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here I went a bit too defensive and raising errors for known errors.
Mainly because, by allowing other illegal constructs, we get more detailed/friendlier errors down the line where we do lot more tree traversal anyway.

Do you suggest we go a bit more aggressive here? It could mean we will have to do go up and down the ast a bit more.

{
return AST_OK;
}
kapilash marked this conversation as resolved.
Show resolved Hide resolved
}
default: {}
}

ast_error(opt->check.errors, term,
"Consume expressions must specify a single identifier");
"Consume expressions must specify an identifier or field");
return AST_ERROR;
}

Expand Down
66 changes: 58 additions & 8 deletions test/libponyc/refer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -841,12 +841,62 @@ TEST_F(ReferTest, ConsumeParamVarSubfieldReassignSameExpressionReuse)

TEST_F(ReferTest, ConsumeTupleAccessorOfFunctionValResult)
{
const char* src =
"actor Main\n"
"new create(env: Env) =>\n"
"let a = \"I am a string\"\n"
"consume a.chop(1)._1";

TEST_ERRORS_1(src,
"consume expressions must specify a single identifier");
const char* src =
"actor Main\n"
"new create(env: Env) =>\n"
"let a = \"I am a string\"\n"
"consume a.chop(1)._1";

TEST_ERRORS_1(src,
"Consume expressions must specify an identifier or field");
}

TEST_F(ReferTest, DoubleConsumeWithFieldAccessor)
{
const char* src =
"class Problem\n"
"let _unwrap_me: U8 = 0\n"
"fun iso unwrap() =>\n"
"let this_iso: Problem iso = consume this\n"
"consume (consume this_iso)._unwrap_me";

TEST_ERRORS_1(src,
"Consume expressions must specify an identifier or field");
}

TEST_F(ReferTest, ConsumeAfterMemberAccessWithConsumeLhs)
{
const char* src =
"actor Main\n"
"new create(env:Env val) =>\n"
"let f : Foo iso = recover Foo end\n"
"(consume f).b = recover Bar end\n"
"let x:Foo tag = consume f\n"
"class Foo\n"
"var b: Bar iso\n"
"new create() =>\n"
"b = recover Bar end\n"
"class Bar\n"
"new create() => None\n";

TEST_ERRORS_2(src,
"can't use a consumed local or field in an expression",
"consume must take 'this', a local, or a parameter");
}

TEST_F(ReferTest, MemberAccessWithConsumeLhs)
{
const char* src =
"actor Main\n"
"new create(env:Env val) =>\n"
"let f : Foo iso = recover Foo end\n"
"(consume f).b = recover Bar end\n"
"class Foo\n"
"var b: Bar iso\n"
"new create() =>\n"
"b = recover Bar end\n"
"class Bar\n"
"new create() => None\n";

TEST_COMPILE(src);
}