diff --git a/src/aeso_code_analysis.erl b/src/aeso_code_analysis.erl new file mode 100644 index 0000000..5c78f0f --- /dev/null +++ b/src/aeso_code_analysis.erl @@ -0,0 +1,214 @@ +-module(aeso_code_analysis). + +-export([analyze/1]). + +%-record(env, {vars = [] :: list()}). +% +%-type env() :: #env{}. + +-record(analysis, + { defined_types = sets:new() :: sets:set() + , defined_funs = sets:new() :: sets:set() + , used_types = sets:new() :: sets:set() + , used_funs = sets:new() :: sets:set() + , used_includes = sets:new() :: sets:set() + , unused_stateful = sets:new() :: sets:set() + %, used_vars = [] :: list() + %, defined_vars = [] :: list() + %, used_consts = [] :: list() + %, shadowing_pairs = [] :: list() + %, unused_retval = [] :: list() + }). + +%-type analysis() :: #analysis{}. + +-define(P(X), io:format(??X ++ ":\n~p\n", [X])). + +-record(alg, + { zero :: A + , plus :: fun((A, A) -> A) + , scoped :: fun((A, A) -> A) }). + +work(expr, X = {qid, Ann, Fun}) -> + ?P(X), + ?P(aeso_syntax:get_ann(stateful, Ann, none)), + ?P(""), + #analysis{used_funs = sets:from_list([Fun])}; +work(type, {qid, _, Type}) -> + #analysis{used_types = sets:from_list([Type])}; +work(bind_type, {id, _, Type}) -> + #analysis{defined_types = sets:from_list([Type])}; +work(_, _) -> + #analysis{}. + +-spec analyze(aeso_syntax:ast()) -> ok. +analyze([{Contract, _Ann, {con, _, _ConName}, _Impls, Decls} | Rest]) + when Contract == contract_main; + Contract == contract_child -> + analyze_contract(Decls), + analyze(Rest); +analyze([{namespace, _Ann, {con, _, _NSName}, Decls} | Rest]) -> + analyze_contract(Decls), + analyze(Rest); +analyze([]) -> + error("End of code analysis"). + +analyze_contract(Decls) -> + Funs = [ Fun || Fun = {letfun, _, _, _, _, _} <- Decls], + _Types = [ Def || Def = {type_def, _, _, _, _} <- Decls ], + _Consts = [ Const || Const = {letval, _, _, _} <- Decls ], + + Alg = + #alg{ zero = #analysis{} + , plus = fun merge/2 + , scoped = fun merge/2 }, + Res0 = aeso_syntax_utils:fold( + Alg, + fun work/2, + decl, + Decls), + Res = merge(Res0, #analysis{defined_funs = Funs}), + UsedTypes = sets:to_list(Res#analysis.used_types), + UsedFuns = sets:to_list(Res#analysis.used_funs), + ?P(UsedFuns), + ?P(UsedTypes), + + %Res = [ {Name, analyze_fun(Fun)} || Fun = {letfun, _, {id, _, Name}, _, _, _} <- Funs ], + + ?P(Res). +% +%analyze_fun({letfun, _, _FunId, _Args, _, GuardedBodies}) -> +% lists:foldl(fun merge/2, #analysis{}, [(analyze_expr(#env{}, Body)) || {guarded, _, _, Body} <- GuardedBodies ]). +% +%-spec analyze_expr(env(), aeso_syntax:expr()) -> analysis(). +%analyze_expr(Env, {lam, _, Args, Expr}) -> +% NewVars = [ Id || {arg, _, Id, _} <- Args ], +% Env1 = lists:foldl(fun bind_var/2, Env, NewVars), +% Analysis = analyze_expr(Env1, Expr), +% Analysis#analysis{defined_vars = Analysis#analysis.defined_vars ++ NewVars}; +%%analyze_expr(Env, {'if', ann(), expr(), expr(), expr()}) -> +%% ok; +%%analyze_expr(Env, {switch, ann(), expr(), [alt()]}) -> +%% ok; +%analyze_expr(Env, {app, _, Expr, _Args}) -> +% analyze_expr(Env, Expr); +%%analyze_expr(Env, {proj, ann(), expr(), id()}) -> +%% ok; +%analyze_expr(Env, {tuple, _, Exprs}) -> +% lists:foldl(fun merge/2, #analysis{}, [ analyze_expr(Env, Expr) || Expr <- Exprs ]); +%%analyze_expr(Env, {list, ann(), [expr()]}) -> +%% ok; +%%analyze_expr(Env, {list_comp, ann(), expr(), [comprehension_exp()]}) -> +%% ok; +%analyze_expr(Env, {typed, _, Expr, Type}) -> +% merge(analyze_expr(Env, Expr), analyze_type(Env, Type)); +%%analyze_expr(Env, {record_or_map(), ann(), [field(expr())]}) -> +%% ok; +%%analyze_expr(Env, {record_or_map(), ann(), expr(), [field(expr())]}) -> +%% ok; +%%analyze_expr(Env, {map, ann(), [{expr(), expr()}]}) -> +%% ok; +%%analyze_expr(Env, {map_get, ann(), expr(), expr()}) -> +%% ok; +%%analyze_expr(Env, {map_get, ann(), expr(), expr(), expr()}) -> +%% ok; +%analyze_expr(Env, {block, _, Stmts}) -> +% analyze_block(Env, Stmts); +%%analyze_expr(Env, {op(), ann()}) -> +%% ok; +%analyze_expr(_Env, {int, _, _}) -> +% #analysis{}; +%analyze_expr(Env, Id = {id, _, _}) -> +% #analysis{used_vars = [lookup_var(Id, Env)]}; +%analyze_expr(_Env, {qcon, _, _}) -> +% #analysis{}. +%%analyze_expr(Env, QId = {qid, _, _}) -> +%% #analysis{used_funs = lookup_fun(QId, Env)}. +%%analyze_expr(Env, qid() | con() | qcon()) -> +%% ok; +%%analyze_expr(Env, constant()) -> +%% ok; +%%analyze_expr(Env, letpat()) -> +%% ok. +% +%analyze_block(_Env, []) -> +% #analysis{}; +%analyze_block(Env, [E]) -> +% analyze_expr(Env, E); +%analyze_block(Env, [{letval, _, {typed, _, Id = {id, _, _}, _}, Expr} | Stmts]) -> +% Env1 = bind_var(Id, Env), +% Analysis = analyze_block(Env1, Stmts), +% Analysis2 = merge(Analysis, analyze_expr(Env1, Expr)), +% Analysis2#analysis{defined_vars = Analysis2#analysis.defined_vars ++ [Id]}. +% +%analyze_type(_Env, {id, _, Id}) -> +% #analysis{used_types = [Id]}; +%analyze_type(_Env, {qid, _, QId}) -> +% #analysis{used_types = [QId]}; +%analyze_type(Env, {fun_t, _, _, Types, Type}) -> +% merge(lists:foldl(fun merge/2, #analysis{}, [analyze_type(Env, T) || T <- Types]), analyze_type(Env, Type)); +%analyze_type(_Env, {tuple_t, _, _}) -> +% #analysis{}. +% +%bind_var(VarId, Env = #env{vars = Vars}) -> +% check_shadowing(VarId, Vars), +% Env#env{vars = [VarId | Vars]}. +% +%check_shadowing(Var = {id, _, VarName}, Ids) -> +% Shadowed = +% lists:search(fun({id, _, Name}) when Name == VarName -> true; +% (_) -> false +% end, Ids), +% case Shadowed of +% false -> +% #analysis{}; +% {value, ShadowedVar} -> +% #analysis{shadowing_pairs = [{Var, ShadowedVar}]} +% end. +% +%lookup_var({id, _, VarName}, Env) -> +% {value, Var}= +% lists:search( +% fun({id, _, Name}) when Name == VarName -> true; +% (_) -> false +% end, Env#env.vars), +% Var. + + +merge(A1, A2) -> + #analysis{ used_types = sets:union(A1#analysis.used_types, A2#analysis.used_types) + , used_funs = sets:union(A1#analysis.used_funs, A2#analysis.used_funs) }. + + +%% the set of unused functions is the different between the 2 sets: +%% - In a contract: +%% * all user-defined functions +%% * all function calls +%% - In a namespace: +%% * all user-defined private functions +%% * all function calls +%% Note: this only applies to functions, not entrypoints + +%% Unused variables: +%% ----------------- +%% Scope: function +%% Algo: if a variable is not used in its current scope, then it's reported. +%% Functions that are declared in one scope but not used, and then declared +%% in a different scope and used, should be considered. +%% +%% Unused stateful: +%% ---------------- +%% Scope: function +%% Algo: check all function calls in the function, if none of them requires +%% stateful, then we say that it's unused. +%% +%% Unused constants: +%% ----------------- +%% Scope: contract or namespace +%% Algo: same as variables, but the scope is fixed to the contract or the namespace. +%% +%% Unused typedefs: +%% ---------------- +%% Scope: contract or namespace +%% Algo: check the types of all expressions in the contract or namespace, if the type +%% doesn't show up anywhere, then the typedef is not used.