From ee03442ddfc76faf2c21d7af3ec1301cd5ddc773 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 27 May 2019 11:58:09 +0200 Subject: [PATCH 1/2] Return parse error instead of crashing the type checker --- src/aeso_parser.erl | 22 ++++++++++++++-------- src/aeso_pretty.erl | 1 + 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index f96505d..1e1ae01 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -211,7 +211,7 @@ exprAtom() -> , ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int))) , {bool, keyword(true), true} , {bool, keyword(false), false} - , ?RULE(brace_list(?LAZY_P(field_assignment())), record(_1)) + , ?LET_P(Fs, brace_list(?LAZY_P(field_assignment())), record(Fs)) , {list, [], bracket_list(Expr)} , ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4)) , ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2)) @@ -254,14 +254,20 @@ record_update(Ann, E, Flds) -> record([]) -> {map, [], []}; record(Fs) -> case record_or_map(Fs) of - record -> {record, get_ann(hd(Fs)), Fs}; + record -> + Fld = fun({field, _, [_], _} = F) -> F; + ({field, Ann, LV, Id, _}) -> + bad_expr_err("Cannot use '@' in record construction", infix({lvalue, Ann, LV}, {'@', Ann}, Id)); + ({field, Ann, LV, _}) -> + bad_expr_err("Cannot use nested fields or keys in record construction", {lvalue, Ann, LV}) end, + {record, get_ann(hd(Fs)), lists:map(Fld, Fs)}; map -> Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val}; - ({field, _, LV, Id, _}) -> - bad_expr_err("Cannot use '@' in map construction", infix(LV, {op, Ann, '@'}, Id)); - ({field, _, LV, _}) -> - bad_expr_err("Cannot use nested fields or keys in map construction", LV) end, + ({field, FAnn, LV, Id, _}) -> + bad_expr_err("Cannot use '@' in map construction", infix({lvalue, FAnn, LV}, {'@', Ann}, Id)); + ({field, FAnn, LV, _}) -> + bad_expr_err("Cannot use nested fields or keys in map construction", {lvalue, FAnn, LV}) end, {map, Ann, lists:map(KV, Fs)} end. @@ -491,11 +497,11 @@ return_error({no_file, L, C}, Err) -> return_error({F, L, C}, Err) -> fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])). --spec ret_doc_err(ann(), prettypr:document()) -> no_return(). +-spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()). ret_doc_err(Ann, Doc) -> return_error(ann_pos(Ann), prettypr:format(Doc)). --spec bad_expr_err(string(), aeso_syntax:expr()) -> no_return(). +-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()). bad_expr_err(Reason, E) -> ret_doc_err(get_ann(E), prettypr:sep([prettypr:text(Reason ++ ":"), diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 094138d..fdcd06d 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -361,6 +361,7 @@ stmt_p({else, Else}) -> -spec bin_prec(aeso_syntax:bin_op()) -> {integer(), integer(), integer()}. bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]' bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]' +bin_prec('@') -> { 0, 0, 0}; %% Only in error messages bin_prec('||') -> {200, 300, 200}; bin_prec('&&') -> {300, 400, 300}; bin_prec('<') -> {400, 500, 500}; From 96547ea2ec24e93ee3de839cc6e090a026b30dca Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 27 May 2019 12:04:38 +0200 Subject: [PATCH 2/2] Test for record field parse error --- test/aeso_compiler_tests.erl | 3 ++ test/aeso_parser_tests.erl | 2 +- test/contracts/field_parse_error.aes | 5 +++ test/contracts/withdrawal.aes | 56 ---------------------------- 4 files changed, 9 insertions(+), 57 deletions(-) create mode 100644 test/contracts/field_parse_error.aes delete mode 100644 test/contracts/withdrawal.aes diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 7d161ff..2a6ac0e 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -329,4 +329,7 @@ failing_contracts() -> <<"The init function should return the initial state as its result and cannot read the state,\n" "but it calls\n" " - state (at line 13, column 13)">>]} + , {"field_parse_error", + [<<"line 6, column 1: In field_parse_error at 5:26:\n" + "Cannot use nested fields or keys in record construction: p.x\n">>]} ]. diff --git a/test/aeso_parser_tests.erl b/test/aeso_parser_tests.erl index bebdd91..dcbbcd0 100644 --- a/test/aeso_parser_tests.erl +++ b/test/aeso_parser_tests.erl @@ -62,7 +62,7 @@ simple_contracts_test_() -> %% Parse tests of example contracts [ {lists:concat(["Parse the ", Contract, " contract."]), fun() -> roundtrip_contract(Contract) end} - || Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, withdrawal, fundme, dutch_auction] ] + || Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, fundme, dutch_auction] ] }. parse_contract(Name) -> diff --git a/test/contracts/field_parse_error.aes b/test/contracts/field_parse_error.aes new file mode 100644 index 0000000..719b32a --- /dev/null +++ b/test/contracts/field_parse_error.aes @@ -0,0 +1,5 @@ + +contract Fail = + record pt = {x : int, y : int} + record r = {p : pt} + function fail() = {p.x = 0, p.y = 0} diff --git a/test/contracts/withdrawal.aes b/test/contracts/withdrawal.aes deleted file mode 100644 index b80f5c9..0000000 --- a/test/contracts/withdrawal.aes +++ /dev/null @@ -1,56 +0,0 @@ -/* Example from Solidity by Example - http://solidity.readthedocs.io/en/develop/common-patterns.html - - contract WithdrawalContract { - address public richest - uint public mostSent - - mapping (address => uint) pendingWithdrawals - - function WithdrawalContract() payable { - richest = msg.sender - mostSent = msg.value - } - - function becomeRichest() payable returns (bool) { - if (msg.value > mostSent) { - pendingWithdrawals[richest] += msg.value - richest = msg.sender - mostSent = msg.value - return true - } else { - return false - } - } - - function withdraw() { - uint amount = pendingWithdrawals[msg.sender] - // Remember to zero the pending refund before - // sending to prevent re-entrancy attacks - pendingWithdrawals[msg.sender] = 0 - msg.sender.transfer(amount) - } - } - -*/ - -contract WithdrawalContract = - - record state = { richest : address, - mostSent : uint, - pendingWithdrawals : map(address, uint) } - - function becomeRichest() : result(bool) = - if (call().value > state.mostSent) - let totalAmount : uint = Map.get_(state.richest, pendingWithdrawals) + call().value - {state = state{ pendingWithdrawals = Map.insert(state.richest, call().value, state.pendingWithdrawals), - richest = call().sender, - mostSent = call().value }, - result = true} - else - {result = false} - - function withdraw() = - let amount : uint = Map.get_(call().sender, state.pendingWithdrawals) - { state.pendingWithdrawals = Map.insert(call().sender, 0, state.pendingWithdrawals), - transactions = spend_tx(amount, call().sender) }