Make aeso_compiler errors structured as well

This commit is contained in:
Hans Svensson 2019-09-05 14:20:40 +02:00
parent 37a37a169d
commit 5a1acd9d18
4 changed files with 113 additions and 57 deletions

View File

@ -8,7 +8,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeso_code_errors). -module(aeso_code_errors).
-export([format/1]). -export([format/1, pos/1]).
format({last_declaration_must_be_contract, Decl = {namespace, _, {con, _, C}, _}}) -> format({last_declaration_must_be_contract, Decl = {namespace, _, {con, _, C}, _}}) ->
Msg = io_lib:format("Expected a contract as the last declaration instead of the namespace '~s'\n", Msg = io_lib:format("Expected a contract as the last declaration instead of the namespace '~s'\n",

View File

@ -153,10 +153,6 @@ string_to_code(ContractString, Options) ->
type_env => TypeEnv} type_env => TypeEnv}
end. end.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
-define(CALL_NAME, "__call"). -define(CALL_NAME, "__call").
-define(DECODE_NAME, "__decode"). -define(DECODE_NAME, "__decode").
@ -223,10 +219,7 @@ check_call1(ContractString0, FunName, Args, Options) ->
{ok, FunName, CallArgs} {ok, FunName, CallArgs}
end end
catch catch
throw:{error, Errors} -> {error, Errors}; throw:{error, Errors} -> {error, Errors}
error:{badmatch, {error, missing_call_function}} ->
{error, join_errors("Type errors", ["missing __call function"],
fun (E) -> E end)}
end. end.
arguments_of_body(CallName, _FunName, Fcode) -> arguments_of_body(CallName, _FunName, Fcode) ->
@ -286,12 +279,19 @@ to_sophia_value(_, _, revert, Data, Options) ->
case proplists:get_value(backend, Options, aevm) of case proplists:get_value(backend, Options, aevm) of
aevm -> aevm ->
case aeb_heap:from_binary(string, Data) of case aeb_heap:from_binary(string, Data) of
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}; {ok, Err} ->
{error, _} = Err -> Err {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} ->
Msg = "Could not interpret the revert message\n",
{error, [aeso_errors:new(data_error, aeso_errors:pos(0, 0), Msg)]}
end; end;
fate -> fate ->
Err = aeb_fate_encoding:deserialize(Data), try aeb_fate_encoding:deserialize(Data) of
{ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}} Err -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}
catch _:_ ->
Msg = "Could not deserialize the revert message\n",
{error, [aeso_errors:new(data_error, aeso_errors:pos(0, 0), Msg)]}
end
end; end;
to_sophia_value(ContractString, FunName, ok, Data, Options0) -> to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Options = [no_code | Options0], Options = [no_code | Options0],
@ -311,31 +311,30 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)} {ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)), Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n", Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[Data, VmType, Type0Str]))], [Data, VmType, Type0Str]),
fun (E) -> E end)} {error, [aeso_errors:new(type_error, aeso_errors:pos(0, 0), Msg)]}
end; end;
{error, _Err} -> {error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))], Msg = io_lib:format("Failed to decode binary as type ~p\n", [VmType]),
fun(E) -> E end)} {error, [aeso_errors:new(code_error, aeso_errors:pos(0, 0), Msg)]}
end; end;
fate -> fate ->
try try
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))} {ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
{error, join_errors("Translation error", Type1 = prettypr:format(aeso_pretty:type(Type)),
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n", Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n",
[aeb_fate_encoding:deserialize(Data), Type]))], [aeb_fate_encoding:deserialize(Data), Type1]),
fun (E) -> E end)}; {error, [aeso_errors:new(type_error, aeso_errors:pos(0, 0), Msg)]};
_:R -> _:_ ->
{error, iolist_to_binary(io_lib:format("Decode error ~p: ~p\n", [R, erlang:get_stacktrace()]))} Type1 = prettypr:format(aeso_pretty:type(Type)),
Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]),
{error, [aeso_errors:new(code_error, aeso_errors:pos(0, 0), Msg)]}
end end
end end
catch catch
throw:{error, Errors} -> {error, Errors}; throw:{error, Errors} -> {error, Errors}
error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)}
end. end.
@ -394,15 +393,14 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
%% Values are Sophia expressions in AST format %% Values are Sophia expressions in AST format
{ok, ArgTypes, Values} {ok, ArgTypes, Values}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)), Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n", [VmValue, VmType, Type0Str]),
[VmValue, VmType, Type0Str]))], {error, [aeso_errors:new(type_error, aeso_errors:pos(0, 0), Msg)]}
fun (E) -> E end)}
end; end;
{error, _Err} -> {error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))], Msg = io_lib:format("Failed to decode calldata as type ~p\n", [VmType]),
fun(E) -> E end)} {error, [aeso_errors:new(code_error, aeso_errors:pos(0, 0), Msg)]}
end; end;
fate -> fate ->
case aeb_fate_abi:decode_calldata(FunName, Calldata) of case aeb_fate_abi:decode_calldata(FunName, Calldata) of
@ -414,30 +412,30 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
{ok, ArgTypes, AstArgs} {ok, ArgTypes, AstArgs}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)), Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n",
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n", [FateArgs, Type0Str]),
[FateArgs, Type0Str]))], {error, [aeso_errors:new(type_error, aeso_errors:pos(0, 0), Msg)]}
fun (E) -> E end)}
end; end;
{error, _} -> {error, _} ->
{error, join_errors("Decode errors", ["Failed to decode binary"], Msg = io_lib:format("Failed to decode calldata binary\n", []),
fun(E) -> E end)} {error, [aeso_errors:new(code_error, aeso_errors:pos(0, 0), Msg)]}
end end
end end
catch catch
throw:{error, Errors} -> throw:{error, Errors} -> {error, Errors}
{error, Errors};
error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)}
end. end.
get_arg_icode(Funs) -> get_arg_icode(Funs) ->
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
[Args] -> Args; [Args] -> Args;
[] -> error({missing_call_function, Funs}) [] -> error_missing_call_function()
end. end.
error_missing_call_function() ->
Msg = "Internal error: missing '__call'-function",
Pos = aeso_errors:pos(0, 0),
aeso_errors:throw(aeso_errors:new(internal_error, Pos, Msg)).
get_call_type([{contract, _, _, Defs}]) -> get_call_type([{contract, _, _, Defs}]) ->
case [ {lists:last(QFunName), FunType} case [ {lists:last(QFunName), FunType}
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
@ -445,13 +443,13 @@ get_call_type([{contract, _, _, Defs}]) ->
{app, _, {app, _,
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of {typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
[Call] -> {ok, Call}; [Call] -> {ok, Call};
[] -> {error, missing_call_function} [] -> error_missing_call_function()
end; end;
get_call_type([_ | Contracts]) -> get_call_type([_ | Contracts]) ->
%% The __call should be in the final contract %% The __call should be in the final contract
get_call_type(Contracts). get_call_type(Contracts).
get_decode_type(FunName, [{contract, _, _, Defs}]) -> get_decode_type(FunName, [{contract, Ann, _, Defs}]) ->
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}]; GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}]; ({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
(_) -> [] end, (_) -> [] end,
@ -460,7 +458,10 @@ get_decode_type(FunName, [{contract, _, _, Defs}]) ->
[] -> [] ->
case FunName of case FunName of
"init" -> {ok, [], {tuple_t, [], []}}; "init" -> {ok, [], {tuple_t, [], []}};
_ -> {error, missing_function} _ ->
Msg = io_lib:format("Function '~s' is missing in contract\n", [FunName]),
Pos = aeso_code_errors:pos(Ann),
aeso_errors:throw(aeso_errors:new(code_error, Pos, Msg))
end end
end; end;
get_decode_type(FunName, [_ | Contracts]) -> get_decode_type(FunName, [_ | Contracts]) ->

View File

@ -15,7 +15,8 @@
}). }).
-type pos() :: #pos{}. -type pos() :: #pos{}.
-type error_type() :: type_error | parse_error | code_error | file_error | internal_error. -type error_type() :: type_error | parse_error | code_error
| file_error | data_error | internal_error.
-record(err, { pos = #pos{} :: pos() -record(err, { pos = #pos{} :: pos()
, type :: error_type() , type :: error_type()
@ -71,9 +72,11 @@ str_pos(#pos{file = F, line = L, col = C}) ->
type(#err{ type = Type }) -> Type. type(#err{ type = Type }) -> Type.
pp(#err{ pos = Pos } = Err) -> pp(#err{ pos = Pos } = Err) ->
lists:flatten(io_lib:format("~s\n~s", [pp_pos(Pos), msg(Err)])). lists:flatten(io_lib:format("~s~s", [pp_pos(Pos), msg(Err)])).
pp_pos(#pos{file = no_file, line = 0, col = 0}) ->
"";
pp_pos(#pos{file = no_file, line = L, col = C}) -> pp_pos(#pos{file = no_file, line = L, col = C}) ->
io_lib:format("At line ~p, col ~p:", [L, C]); io_lib:format("At line ~p, col ~p:\n", [L, C]);
pp_pos(#pos{file = F, line = L, col = C}) -> pp_pos(#pos{file = F, line = L, col = C}) ->
io_lib:format("In '~s' at line ~p, col ~p:", [F, L, C]). io_lib:format("In '~s' at line ~p, col ~p:\n", [F, L, C]).

View File

@ -72,6 +72,58 @@ encode_decode_sophia_test() ->
ok = Check("r", "{x = (\"foo\", 0), y = Red}"), ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
ok. ok.
to_sophia_value_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
{error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)),
?assertEqual("Failed to decode binary as type string\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]),
?assertEqual("Failed to decode binary as type string\n", aeso_errors:pp(Err2)),
{error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)),
?assertEqual("Could not interpret the revert message\n", aeso_errors:pp(Err3)),
{error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]),
?assertEqual("Could not deserialize the revert message\n", aeso_errors:pp(Err4)),
ok.
encode_calldata_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
ExpErr1 = "At line 5, col 34:\nCannot unify int\n and bool\n"
"when checking the application at line 5, column 34 of\n"
" x : (int) => string\nto arguments\n true : bool\n",
{error, [Err1]} = aeso_compiler:create_calldata(Code, "x", ["true"]),
?assertEqual(ExpErr1, aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:create_calldata(Code, "x", ["true"], [{backend, fate}]),
?assertEqual(ExpErr1, aeso_errors:pp(Err2)),
ok.
decode_calldata_neg_test() ->
Code1 = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
Code2 = [ "contract Foo =\n"
" entrypoint x(y : string) : int = 42\n" ],
{ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]),
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]),
{error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM),
?assertEqual("Failed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]),
?assertEqual("Failed to decode calldata binary\n", aeso_errors:pp(Err2)),
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]),
?assertEqual("Cannot translate FATE value \"*\"\n of Sophia type (string)\n", aeso_errors:pp(Err3)),
{error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM),
?assertEqual("At line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)),
{error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]),
?assertEqual("At line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)),
ok.
encode_decode_sophia_string(SophiaType, String) -> encode_decode_sophia_string(SophiaType, String) ->
io:format("String ~p~n", [String]), io:format("String ~p~n", [String]),
Code = [ "contract MakeCall =\n" Code = [ "contract MakeCall =\n"