diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/typer | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/typer')
-rw-r--r-- | lib/typer/Makefile | 43 | ||||
-rw-r--r-- | lib/typer/ebin/.gitignore | 0 | ||||
-rw-r--r-- | lib/typer/src/Makefile | 121 | ||||
-rw-r--r-- | lib/typer/src/typer.app.src | 14 | ||||
-rw-r--r-- | lib/typer/src/typer.appup.src | 1 | ||||
-rw-r--r-- | lib/typer/src/typer.erl | 197 | ||||
-rw-r--r-- | lib/typer/src/typer.hrl | 64 | ||||
-rw-r--r-- | lib/typer/src/typer_annotator.erl | 382 | ||||
-rw-r--r-- | lib/typer/src/typer_info.erl | 162 | ||||
-rw-r--r-- | lib/typer/src/typer_map.erl | 47 | ||||
-rw-r--r-- | lib/typer/src/typer_options.erl | 191 | ||||
-rw-r--r-- | lib/typer/src/typer_preprocess.erl | 154 | ||||
-rw-r--r-- | lib/typer/vsn.mk | 1 |
13 files changed, 1377 insertions, 0 deletions
diff --git a/lib/typer/Makefile b/lib/typer/Makefile new file mode 100644 index 0000000000..40a82e9bba --- /dev/null +++ b/lib/typer/Makefile @@ -0,0 +1,43 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2006-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +#============================================================================= +# +# File: lib/typer/Makefile +# Authors: Bingwen He, Tobias Lindahl, and Kostis Sagonas +# +#============================================================================= +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# +# Macros +# + +SUB_DIRECTORIES = src + +include vsn.mk +VSN = $(TYPER_VSN) + +SPECIAL_TARGETS = + +# +# Default Subdir Targets +# +include $(ERL_TOP)/make/otp_subdir.mk + diff --git a/lib/typer/ebin/.gitignore b/lib/typer/ebin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/typer/ebin/.gitignore diff --git a/lib/typer/src/Makefile b/lib/typer/src/Makefile new file mode 100644 index 0000000000..9c9ef6156f --- /dev/null +++ b/lib/typer/src/Makefile @@ -0,0 +1,121 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2006-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +#============================================================================= +# +# File: lib/typer/src/Makefile +# Authors: Kostis Sagonas +# +#============================================================================= + +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(TYPER_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/typer-$(VSN) + +# ---------------------------------------------------- +# Orientation information -- find dialyzer's dir +# ---------------------------------------------------- +DIALYZER_DIR = $(ERL_TOP)/lib/dialyzer + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +MODULES = \ + typer \ + typer_annotator \ + typer_info \ + typer_map \ + typer_options \ + typer_preprocess + +HRL_FILES= typer.hrl +ERL_FILES= $(MODULES:%=%.erl) +INSTALL_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET) +TARGET_FILES= $(INSTALL_FILES) + +APP_FILE= typer.app +APP_SRC= $(APP_FILE).src +APP_TARGET= $(EBIN)/$(APP_FILE) + +APPUP_FILE= typer.appup +APPUP_SRC= $(APPUP_FILE).src +APPUP_TARGET= $(EBIN)/$(APPUP_FILE) + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += +warn_exported_vars +warn_untyped_record +warn_missing_spec + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(TARGET_FILES) + +docs: + +clean: + rm -f $(TARGET_FILES) + rm -f core + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- + +$(EBIN)/typer_options.$(EMULATOR): typer_options.erl ../vsn.mk Makefile + erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) typer_options.erl + +$(APP_TARGET): $(APP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +# --------------------------------------------------------------------- +# dependencies -- I wish they were somehow automatically generated +# --------------------------------------------------------------------- + +$(EBIN)/typer.beam: typer.hrl +$(EBIN)/typer_annotator.beam: typer.hrl +$(EBIN)/typer_info.beam: typer.hrl +$(EBIN)/typer_options.beam: typer.hrl +$(EBIN)/typer_preprocess.beam: typer.hrl + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(YRL_FILES) \ + $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(INSTALL_FILES) $(RELSYSDIR)/ebin + +release_docs_spec: diff --git a/lib/typer/src/typer.app.src b/lib/typer/src/typer.app.src new file mode 100644 index 0000000000..3eb0cbf816 --- /dev/null +++ b/lib/typer/src/typer.app.src @@ -0,0 +1,14 @@ +% This is an -*- erlang -*- file. + +{application, typer, + [{description, "TYPe annotator for ERlang programs, version %VSN%"}, + {vsn, "%VSN%"}, + {modules, [typer, + typer_annotator, + typer_info, + typer_map, + typer_options, + typer_preprocess]}, + {registered, []}, + {applications, [compiler, dialyzer, hipe, kernel, stdlib]}, + {env, []}]}. diff --git a/lib/typer/src/typer.appup.src b/lib/typer/src/typer.appup.src new file mode 100644 index 0000000000..54a63833e6 --- /dev/null +++ b/lib/typer/src/typer.appup.src @@ -0,0 +1 @@ +{"%VSN%",[],[]}. diff --git a/lib/typer/src/typer.erl b/lib/typer/src/typer.erl new file mode 100644 index 0000000000..cebe6ea488 --- /dev/null +++ b/lib/typer/src/typer.erl @@ -0,0 +1,197 @@ +%% -*- erlang-indent-level: 2 -*- +%%----------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%-------------------------------------------------------------------- +%% File : typer.erl +%% Author : Bingwen He <[email protected]> +%% Description : The main driver of the TypEr application +%%-------------------------------------------------------------------- + +-module(typer). + +-export([start/0]). +-export([error/1, compile_error/1]). % for error reporting + +-include("typer.hrl"). + +%%-------------------------------------------------------------------- + +-spec start() -> no_return(). + +start() -> + {Args, Analysis} = typer_options:process(), + %% io:format("Args: ~p\n", [Args]), + %% io:format("Analysis: ~p\n", [Analysis]), + TrustedFiles = typer_preprocess:get_all_files(Args, trust), + Analysis1 = Analysis#typer_analysis{t_files = TrustedFiles}, + Analysis2 = extract(Analysis1), + All_Files = typer_preprocess:get_all_files(Args, analysis), + %% io:format("All_Files: ~p\n", [All_Files]), + Analysis3 = Analysis2#typer_analysis{ana_files = All_Files}, + Analysis4 = typer_info:collect(Analysis3), + %% io:format("Final: ~p\n", [Analysis4#typer_analysis.final_files]), + TypeInfo = get_type_info(Analysis4), + typer_annotator:annotate(TypeInfo), + %% io:format("\nTyper analysis finished\n"), + erlang:halt(0). + +%%-------------------------------------------------------------------- + +-spec extract(#typer_analysis{}) -> #typer_analysis{}. + +extract(#typer_analysis{macros = Macros, includes = Includes, + t_files = TFiles, trust_plt = TrustPLT} = Analysis) -> + %% io:format("--- Extracting trusted typer_info... "), + Ds = [{d, Name, Value} || {Name, Value} <- Macros], + CodeServer = dialyzer_codeserver:new(), + Fun = + fun(File, CS) -> + %% We include one more dir; the one above the one we are trusting + %% E.g, for /home/tests/typer_ann/test.ann.erl, we should include + %% /home/tests/ rather than /home/tests/typer_ann/ + AllIncludes = [filename:dirname(filename:dirname(File)) | Includes], + Is = [{i, Dir} || Dir <- AllIncludes], + CompOpts = dialyzer_utils:src_compiler_opts() ++ Is ++ Ds, + case dialyzer_utils:get_abstract_code_from_src(File, CompOpts) of + {ok, AbstractCode} -> + case dialyzer_utils:get_record_and_type_info(AbstractCode) of + {ok, RecDict} -> + Mod = list_to_atom(filename:basename(File, ".erl")), + case dialyzer_utils:get_spec_info(Mod, AbstractCode, RecDict) of + {ok, SpecDict} -> + CS1 = dialyzer_codeserver:store_temp_records(Mod, RecDict, CS), + dialyzer_codeserver:store_temp_contracts(Mod, SpecDict, CS1); + {error, Reason} -> compile_error([Reason]) + end; + {error, Reason} -> compile_error([Reason]) + end; + {error, Reason} -> compile_error(Reason) + end + end, + CodeServer1 = lists:foldl(Fun, CodeServer, TFiles), + %% Process remote types + NewCodeServer = + try + NewRecords = dialyzer_codeserver:get_temp_records(CodeServer1), + OldRecords = dialyzer_plt:get_types(TrustPLT), % XXX change to the PLT? + MergedRecords = dialyzer_utils:merge_records(NewRecords, OldRecords), + CodeServer2 = dialyzer_codeserver:set_temp_records(MergedRecords, CodeServer1), + CodeServer3 = dialyzer_utils:process_record_remote_types(CodeServer2), + dialyzer_contracts:process_contract_remote_types(CodeServer3) + catch + throw:{error, ErrorMsg} -> + compile_error(ErrorMsg) + end, + %% Create TrustPLT + Contracts = dialyzer_codeserver:get_contracts(NewCodeServer), + Modules = dict:fetch_keys(Contracts), + FoldFun = + fun(Module, TmpPlt) -> + {ok, ModuleContracts} = dict:find(Module, Contracts), + SpecList = [{MFA, Contract} + || {MFA, {_FileLine, Contract}} <- dict:to_list(ModuleContracts)], + dialyzer_plt:insert_contract_list(TmpPlt, SpecList) + end, + NewTrustPLT = lists:foldl(FoldFun, TrustPLT, Modules), + Analysis#typer_analysis{trust_plt = NewTrustPLT}. + +%%-------------------------------------------------------------------- + +-spec get_type_info(#typer_analysis{}) -> #typer_analysis{}. + +get_type_info(#typer_analysis{callgraph = CallGraph, + trust_plt = TrustPLT, + code_server = CodeServer} = Analysis) -> + StrippedCallGraph = remove_external(CallGraph, TrustPLT), + %% io:format("--- Analyzing callgraph... "), + try + NewPlt = dialyzer_succ_typings:analyze_callgraph(StrippedCallGraph, + TrustPLT, CodeServer), + Analysis#typer_analysis{callgraph = StrippedCallGraph, trust_plt = NewPlt} + catch + error:What -> + error(io_lib:format("Analysis failed with message: ~p", + [{What, erlang:get_stacktrace()}])); + throw:{dialyzer_succ_typing_error, Msg} -> + error(io_lib:format("Analysis failed with message: ~s", [Msg])) + end. + +-spec remove_external(dialyzer_callgraph:callgraph(), dialyzer_plt:plt()) -> dialyzer_callgraph:callgraph(). + +remove_external(CallGraph, PLT) -> + {StrippedCG0, Ext} = dialyzer_callgraph:remove_external(CallGraph), + StrippedCG = dialyzer_callgraph:finalize(StrippedCG0), + case get_external(Ext, PLT) of + [] -> ok; + Externals -> + msg(io_lib:format(" Unknown functions: ~p\n", [lists:usort(Externals)])) + end, + StrippedCG. + +-spec get_external([{mfa(), mfa()}], dialyzer_plt:plt()) -> [mfa()]. + +get_external(Exts, Plt) -> + Fun = fun ({_From, To = {M, F, A}}, Acc) -> + case dialyzer_plt:contains_mfa(Plt, To) of + false -> + case erl_bif_types:is_known(M, F, A) of + true -> Acc; + false -> [To|Acc] + end; + true -> Acc + end + end, + lists:foldl(Fun, [], Exts). + +%%-------------------------------------------------------------------- + +-spec error(string()) -> no_return(). + +error(Slogan) -> + msg(io_lib:format("typer: ~s\n", [Slogan])), + erlang:halt(1). + +%%-------------------------------------------------------------------- + +-spec compile_error([string()]) -> no_return(). + +compile_error(Reason) -> + JoinedString = lists:flatten([X ++ "\n" || X <- Reason]), + Msg = "Analysis failed with error report:\n" ++ JoinedString, + error(Msg). + +%%-------------------------------------------------------------------- +%% Outputs a message on 'stderr', if possible. +%%-------------------------------------------------------------------- + +-spec msg(string()) -> 'ok'. + +msg(Msg) -> + case os:type() of + {unix, _} -> + P = open_port({fd, 0, 2}, [out]), + port_command(P, Msg), + true = port_close(P), + ok; + _ -> % win32, vxworks + io:format("~s", [Msg]) + end. + +%%-------------------------------------------------------------------- diff --git a/lib/typer/src/typer.hrl b/lib/typer/src/typer.hrl new file mode 100644 index 0000000000..c331dd82db --- /dev/null +++ b/lib/typer/src/typer.hrl @@ -0,0 +1,64 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-define(SHOW, show). +-define(SHOW_EXPORTED, show_exported). +-define(ANNOTATE, annotate). +-define(ANNOTATE_INC_FILES, annotate_inc_files). + +-type mode() :: ?SHOW | ?SHOW_EXPORTED | ?ANNOTATE | ?ANNOTATE_INC_FILES. + +-record(typer_analysis, + {mode :: mode(), + macros = [] :: [{atom(), _}], % {macro_name, value} + includes = [] :: [string()], + + %% Esp for Dialyzer + %% ---------------------- + code_server = dialyzer_codeserver:new():: dialyzer_codeserver:codeserver(), + callgraph = dialyzer_callgraph:new() :: dialyzer_callgraph:callgraph(), + ana_files = [] :: [string()], % absolute filenames + plt = none :: 'none' | string(), + + %% Esp for TypEr + %% ---------------------- + t_files = [] :: [string()], + + %% For choosing between contracts or comments + contracts = true :: boolean(), + + %% Any file in 'final_files' is compilable. + %% And we need to keep it as {FileName,ModuleName} + %% in case filename does NOT match with moduleName + final_files = [] :: [{string(), atom()}], + + ex_func = typer_map:new() :: dict(), + record = typer_map:new() :: dict(), + + %% Functions: the line number of the function + %% should be kept as well + func = typer_map:new() :: dict(), + inc_func = typer_map:new() :: dict(), + trust_plt = dialyzer_plt:new() :: dialyzer_plt:plt()}). + +-record(args, + {analyze = [] :: [string()], + analyzed_dir_r = [] :: [string()], + trust = [] :: [string()]}). diff --git a/lib/typer/src/typer_annotator.erl b/lib/typer/src/typer_annotator.erl new file mode 100644 index 0000000000..17eeeb6dfe --- /dev/null +++ b/lib/typer/src/typer_annotator.erl @@ -0,0 +1,382 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%============================================================================ +%% File : typer_annotator.erl +%% Author : Bingwen He <[email protected]> +%% Description : +%% If file 'FILENAME' has been analyzed, then the output of +%% command "diff -B FILENAME.erl typer_ann/FILENAME.ann.erl" +%% should be exactly what TypEr has added, namely type info. +%%============================================================================ + +-module(typer_annotator). + +-export([annotate/1]). + +%%---------------------------------------------------------------------------- + +-include("typer.hrl"). + +%%---------------------------------------------------------------------------- + +-define(TYPER_ANN_DIR, "typer_ann"). + +-type func_info() :: {non_neg_integer(), atom(), arity()}. + +-record(info, {recMap = typer_map:new() :: dict(), + funcs = [] :: [func_info()], + typeMap :: dict(), + contracts :: boolean()}). +-record(inc, {map = typer_map:new() :: dict(), + filter = [] :: [string()]}). + +%%---------------------------------------------------------------------------- + +-spec annotate(#typer_analysis{}) -> 'ok'. + +annotate(Analysis) -> + case Analysis#typer_analysis.mode of + ?SHOW -> show(Analysis); + ?SHOW_EXPORTED -> show(Analysis); + ?ANNOTATE -> + Fun = fun({File, Module}) -> + Info = get_final_info(File, Module, Analysis), + write_typed_file(File, Info) + end, + lists:foreach(Fun, Analysis#typer_analysis.final_files); + ?ANNOTATE_INC_FILES -> + IncInfo = write_and_collect_inc_info(Analysis), + write_inc_files(IncInfo) + end. + +write_and_collect_inc_info(Analysis) -> + Fun = fun({File, Module}, Inc) -> + Info = get_final_info(File, Module, Analysis), + write_typed_file(File, Info), + IncFuns = get_functions(File, Analysis), + collect_imported_funcs(IncFuns, Info#info.typeMap, Inc) + end, + NewInc = lists:foldl(Fun,#inc{}, Analysis#typer_analysis.final_files), + clean_inc(NewInc). + +write_inc_files(Inc) -> + Fun = + fun (File) -> + Val = typer_map:lookup(File,Inc#inc.map), + %% Val is function with its type info + %% in form [{{Line,F,A},Type}] + Functions = [Key || {Key,_} <- Val], + Val1 = [{{F,A},Type} || {{_Line,F,A},Type} <- Val], + Info = #info{typeMap = typer_map:from_list(Val1), + recMap = typer_map:new(), + %% Note we need to sort functions here! + funcs = lists:keysort(1, Functions)}, + %% io:format("TypeMap ~p\n", [Info#info.typeMap]), + %% io:format("Funcs ~p\n", [Info#info.funcs]), + %% io:format("RecMap ~p\n", [Info#info.recMap]), + write_typed_file(File, Info) + end, + lists:foreach(Fun, dict:fetch_keys(Inc#inc.map)). + +show(Analysis) -> + Fun = fun({File, Module}) -> + Info = get_final_info(File, Module, Analysis), + show_type_info_only(File, Info) + end, + lists:foreach(Fun, Analysis#typer_analysis.final_files). + +get_final_info(File, Module, Analysis) -> + RecMap = get_recMap(File, Analysis), + TypeMap = get_typeMap(Module, Analysis,RecMap), + Functions = get_functions(File, Analysis), + Contracts = Analysis#typer_analysis.contracts, + #info{recMap=RecMap, funcs=Functions, typeMap=TypeMap, contracts=Contracts}. + +collect_imported_funcs(Funcs, TypeMap, TmpInc) -> + %% Coming from other sourses, including: + %% FIXME: How to deal with yecc-generated file???? + %% --.yrl (yecc-generated file)??? + %% -- yeccpre.hrl (yecc-generated file)??? + %% -- other cases + Fun = fun({File,_} = Obj, Inc) -> + case is_yecc_file(File, Inc) of + {yecc_generated, NewInc} -> NewInc; + {not_yecc, NewInc} -> + check_imported_funcs(Obj, NewInc, TypeMap) + end + end, + lists:foldl(Fun, TmpInc, Funcs). + +-spec is_yecc_file(string(), #inc{}) -> {'not_yecc', #inc{}} + | {'yecc_generated', #inc{}}. +is_yecc_file(File, Inc) -> + case lists:member(File, Inc#inc.filter) of + true -> {yecc_generated, Inc}; + false -> + case filename:extension(File) of + ".yrl" -> + Rootname = filename:rootname(File, ".yrl"), + Obj = Rootname ++ ".erl", + case lists:member(Obj, Inc#inc.filter) of + true -> {yecc_generated, Inc}; + false -> + NewFilter = [Obj|Inc#inc.filter], + NewInc = Inc#inc{filter = NewFilter}, + {yecc_generated, NewInc} + end; + _ -> + case filename:basename(File) of + "yeccpre.hrl" -> {yecc_generated, Inc}; + _ -> {not_yecc, Inc} + end + end + end. + +check_imported_funcs({File, {Line, F, A}}, Inc, TypeMap) -> + IncMap = Inc#inc.map, + FA = {F, A}, + Type = get_type_info(FA, TypeMap), + case typer_map:lookup(File, IncMap) of + none -> %% File is not added. Add it + Obj = {File,[{FA, {Line, Type}}]}, + NewMap = typer_map:insert(Obj, IncMap), + Inc#inc{map = NewMap}; + Val -> %% File is already in. Check. + case lists:keyfind(FA, 1, Val) of + false -> + %% Function is not in; add it + Obj = {File, Val ++ [{FA, {Line, Type}}]}, + NewMap = typer_map:insert(Obj, IncMap), + Inc#inc{map = NewMap}; + Type -> + %% Function is in and with same type + Inc; + _ -> + %% Function is in but with diff type + inc_warning(FA, File), + Elem = lists:keydelete(FA, 1, Val), + NewMap = case Elem of + [] -> + typer_map:remove(File, IncMap); + _ -> + typer_map:insert({File, Elem}, IncMap) + end, + Inc#inc{map = NewMap} + end + end. + +inc_warning({F, A}, File) -> + io:format(" ***Warning: Skip function ~p/~p ", [F, A]), + io:format("in file ~p because of inconsistent type\n", [File]). + +clean_inc(Inc) -> + Inc1 = remove_yecc_generated_file(Inc), + normalize_obj(Inc1). + +remove_yecc_generated_file(TmpInc) -> + Fun = fun(Key, Inc) -> + NewMap = typer_map:remove(Key, Inc#inc.map), + Inc#inc{map = NewMap} + end, + lists:foldl(Fun, TmpInc, TmpInc#inc.filter). + +normalize_obj(TmpInc) -> + Fun = fun(Key, Val, Inc) -> + NewVal = [{{Line,F,A},Type} || {{F,A},{Line,Type}} <- Val], + typer_map:insert({Key,NewVal}, Inc) + end, + NewMap = typer_map:fold(Fun, typer_map:new(), TmpInc#inc.map), + TmpInc#inc{map = NewMap}. + +get_recMap(File, Analysis) -> + typer_map:lookup(File, Analysis#typer_analysis.record). + +get_typeMap(Module, Analysis, RecMap) -> + TypeInfoPlt = Analysis#typer_analysis.trust_plt, + TypeInfo = + case dialyzer_plt:lookup_module(TypeInfoPlt, Module) of + none -> []; + {value, List} -> List + end, + CodeServer = Analysis#typer_analysis.code_server, + TypeInfoList = [get_type(I, CodeServer, RecMap) || I <- TypeInfo], + typer_map:from_list(TypeInfoList). + +get_type({{M, F, A} = MFA, Range, Arg}, CodeServer, RecMap) -> + case dialyzer_codeserver:lookup_mfa_contract(MFA, CodeServer) of + error -> + {{F, A}, {Range, Arg}}; + {ok, {_FileLine, Contract}} -> + Sig = erl_types:t_fun(Arg, Range), + case dialyzer_contracts:check_contract(Contract, Sig) of + ok -> {{F, A}, {contract, Contract}}; + {error, invalid_contract} -> + CString = dialyzer_contracts:contract_to_string(Contract), + SigString = dialyzer_utils:format_sig(Sig, RecMap), + typer:error( + io_lib:format("Error in contract of function ~w:~w/~w\n" + "\t The contract is: " ++ CString ++ "\n" ++ + "\t but the inferred signature is: ~s", + [M, F, A, SigString])); + {error, Msg} -> + typer:error( + io_lib:format("Error in contract of function ~w:~w/~w: ~s", + [M, F, A, Msg])) + end + end. + +get_functions(File, Analysis) -> + case Analysis#typer_analysis.mode of + ?SHOW -> + Funcs = typer_map:lookup(File, Analysis#typer_analysis.func), + Inc_Funcs = typer_map:lookup(File, Analysis#typer_analysis.inc_func), + remove_module_info(Funcs) ++ normalize_incFuncs(Inc_Funcs); + ?SHOW_EXPORTED -> + Ex_Funcs = typer_map:lookup(File, Analysis#typer_analysis.ex_func), + remove_module_info(Ex_Funcs); + ?ANNOTATE -> + Funcs = typer_map:lookup(File, Analysis#typer_analysis.func), + remove_module_info(Funcs); + ?ANNOTATE_INC_FILES -> + typer_map:lookup(File, Analysis#typer_analysis.inc_func) + end. + +normalize_incFuncs(Funcs) -> + [FuncInfo || {_FileName, FuncInfo} <- Funcs]. + +-spec remove_module_info([func_info()]) -> [func_info()]. + +remove_module_info(FuncInfoList) -> + F = fun ({_,module_info,0}) -> false; + ({_,module_info,1}) -> false; + ({Line,F,A}) when is_integer(Line), is_atom(F), is_integer(A) -> true + end, + lists:filter(F, FuncInfoList). + +write_typed_file(File, Info) -> + io:format(" Processing file: ~p\n", [File]), + Dir = filename:dirname(File), + RootName = filename:basename(filename:rootname(File)), + Ext = filename:extension(File), + TyperAnnDir = filename:join(Dir, ?TYPER_ANN_DIR), + TmpNewFilename = lists:concat([RootName,".ann",Ext]), + NewFileName = filename:join(TyperAnnDir, TmpNewFilename), + case file:make_dir(TyperAnnDir) of + {error, Reason} -> + case Reason of + eexist -> %% TypEr dir exists; remove old typer files + ok = file:delete(NewFileName), + write_typed_file(File, Info, NewFileName); + enospc -> + io:format(" Not enough space in ~p\n", [Dir]); + eacces -> + io:format(" No write permission in ~p\n", [Dir]); + _ -> + io:format("Unknown error when writing ~p\n", [Dir]), + halt() + end; + ok -> %% Typer dir does NOT exist + write_typed_file(File, Info, NewFileName) + end. + +write_typed_file(File, Info, NewFileName) -> + {ok, Binary} = file:read_file(File), + Chars = binary_to_list(Binary), + write_typed_file(Chars, NewFileName, Info, 1, []), + io:format(" Saved as: ~p\n", [NewFileName]). + +write_typed_file(Chars, File, #info{funcs = []}, _LNo, _Acc) -> + ok = file:write_file(File, list_to_binary(Chars), [append]); +write_typed_file([Ch|Chs] = Chars, File, Info, LineNo, Acc) -> + [{Line,F,A}|RestFuncs] = Info#info.funcs, + case Line of + 1 -> %% This will happen only for inc files + ok = raw_write(F, A, Info, File, []), + NewInfo = Info#info{funcs = RestFuncs}, + NewAcc = [], + write_typed_file(Chars, File, NewInfo, Line, NewAcc); + _ -> + case Ch of + 10 -> + NewLineNo = LineNo + 1, + {NewInfo, NewAcc} = + case NewLineNo of + Line -> + ok = raw_write(F, A, Info, File, [Ch|Acc]), + {Info#info{funcs = RestFuncs}, []}; + _ -> + {Info, [Ch|Acc]} + end, + write_typed_file(Chs, File, NewInfo, NewLineNo, NewAcc); + _ -> + write_typed_file(Chs, File, Info, LineNo, [Ch|Acc]) + end + end. + +raw_write(F, A, Info, File, Content) -> + TypeInfo = get_type_string(F, A, Info, file), + ContentList = lists:reverse(Content) ++ TypeInfo ++ "\n", + ContentBin = list_to_binary(ContentList), + file:write_file(File, ContentBin, [append]). + +get_type_string(F, A, Info, Mode) -> + Type = get_type_info({F,A}, Info#info.typeMap), + TypeStr = + case Type of + {contract, C} -> + dialyzer_contracts:contract_to_string(C); + {RetType, ArgType} -> + dialyzer_utils:format_sig(erl_types:t_fun(ArgType, RetType), + Info#info.recMap) + end, + case Info#info.contracts of + true -> + case {Mode, Type} of + {file, {contract, _}} -> ""; + _ -> + Prefix = lists:concat(["-spec ", F]), + lists:concat([Prefix, TypeStr, "."]) + end; + false -> + Prefix = lists:concat(["%% @spec ", F]), + lists:concat([Prefix, TypeStr, "."]) + end. + +show_type_info_only(File, Info) -> + io:format("\n%% File: ~p\n%% ", [File]), + OutputString = lists:concat(["~.", length(File)+8, "c~n"]), + io:fwrite(OutputString, [$-]), + Fun = fun ({_LineNo, F, A}) -> + TypeInfo = get_type_string(F, A, Info, show), + io:format("~s\n", [TypeInfo]) + end, + lists:foreach(Fun, Info#info.funcs). + +get_type_info(Func, TypeMap) -> + case typer_map:lookup(Func, TypeMap) of + none -> + %% Note: Typeinfo of any function should exist in + %% the result offered by dialyzer, otherwise there + %% *must* be something wrong with the analysis + io:format("No type info for function: ~p\n", [Func]), + halt(); + {contract, _Fun} = C -> C; + {_RetType, _ArgType} = RA -> RA + end. diff --git a/lib/typer/src/typer_info.erl b/lib/typer/src/typer_info.erl new file mode 100644 index 0000000000..ea25fa6f68 --- /dev/null +++ b/lib/typer/src/typer_info.erl @@ -0,0 +1,162 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(typer_info). + +-export([collect/1]). + +-type func_info() :: {non_neg_integer(), atom(), arity()}. +-type inc_file_info() :: {string(), func_info()}. + +-record(tmpAcc, {file :: string(), + module :: atom(), + funcAcc=[] :: [func_info()], + incFuncAcc=[] :: [inc_file_info()], + dialyzerObj=[] :: [{mfa(), {_, _}}]}). + +-include("typer.hrl"). + +-spec collect(#typer_analysis{}) -> #typer_analysis{}. + +collect(Analysis) -> + NewPlt = + try get_dialyzer_plt(Analysis) of + DialyzerPlt -> + dialyzer_plt:merge_plts([Analysis#typer_analysis.trust_plt, DialyzerPlt]) + catch + throw:{dialyzer_error,_Reason} -> + typer:error("Dialyzer's PLT is missing or is not up-to-date; please (re)create it") + end, + NewAnalysis = lists:foldl(fun collect_one_file_info/2, + Analysis#typer_analysis{trust_plt = NewPlt}, + Analysis#typer_analysis.ana_files), + %% Process Remote Types + TmpCServer = NewAnalysis#typer_analysis.code_server, + NewCServer = + try + NewRecords = dialyzer_codeserver:get_temp_records(TmpCServer), + OldRecords = dialyzer_plt:get_types(NewPlt), + MergedRecords = dialyzer_utils:merge_records(NewRecords, OldRecords), + %% io:format("Merged Records ~p",[MergedRecords]), + TmpCServer1 = dialyzer_codeserver:set_temp_records(MergedRecords, TmpCServer), + TmpCServer2 = dialyzer_utils:process_record_remote_types(TmpCServer1), + dialyzer_contracts:process_contract_remote_types(TmpCServer2) + catch + throw:{error, ErrorMsg} -> + typer:error(ErrorMsg) + end, + NewAnalysis#typer_analysis{code_server = NewCServer}. + +collect_one_file_info(File, Analysis) -> + Ds = [{d,Name,Val} || {Name,Val} <- Analysis#typer_analysis.macros], + %% Current directory should also be included in "Includes". + Includes = [filename:dirname(File)|Analysis#typer_analysis.includes], + Is = [{i,Dir} || Dir <- Includes], + Options = dialyzer_utils:src_compiler_opts() ++ Is ++ Ds, + case dialyzer_utils:get_abstract_code_from_src(File, Options) of + {error, Reason} -> + %% io:format("File=~p\n,Options=~p\n,Error=~p\n", [File,Options,Reason]), + typer:compile_error(Reason); + {ok, AbstractCode} -> + case dialyzer_utils:get_core_from_abstract_code(AbstractCode, Options) of + error -> typer:compile_error(["Could not get core erlang for "++File]); + {ok, Core} -> + case dialyzer_utils:get_record_and_type_info(AbstractCode) of + {error, Reason} -> typer:compile_error([Reason]); + {ok, Records} -> + Mod = list_to_atom(filename:basename(File, ".erl")), + case dialyzer_utils:get_spec_info(Mod, AbstractCode, Records) of + {error, Reason} -> typer:compile_error([Reason]); + {ok, SpecInfo} -> + analyze_core_tree(Core, Records, SpecInfo, Analysis, File) + end + end + end + end. + +analyze_core_tree(Core, Records, SpecInfo, Analysis, File) -> + Module = list_to_atom(filename:basename(File, ".erl")), + TmpTree = cerl:from_records(Core), + CS1 = Analysis#typer_analysis.code_server, + NextLabel = dialyzer_codeserver:get_next_core_label(CS1), + {Tree, NewLabel} = cerl_trees:label(TmpTree, NextLabel), + CS2 = dialyzer_codeserver:insert(Module, Tree, CS1), + CS3 = dialyzer_codeserver:set_next_core_label(NewLabel, CS2), + CS4 = dialyzer_codeserver:store_temp_records(Module, Records, CS3), + CS5 = dialyzer_codeserver:store_temp_contracts(Module, SpecInfo, CS4), + Ex_Funcs = [{0,F,A} || {_,_,{F,A}} <- cerl:module_exports(Tree)], + TmpCG = Analysis#typer_analysis.callgraph, + CG = dialyzer_callgraph:scan_core_tree(Tree, TmpCG), + Fun = fun analyze_one_function/2, + All_Defs = cerl:module_defs(Tree), + Acc = lists:foldl(Fun, #tmpAcc{file=File, module=Module}, All_Defs), + Exported_FuncMap = typer_map:insert({File, Ex_Funcs}, + Analysis#typer_analysis.ex_func), + %% NOTE: we must sort all functions in the file which + %% originate from this file by *numerical order* of lineNo + Sorted_Functions = lists:keysort(1, Acc#tmpAcc.funcAcc), + FuncMap = typer_map:insert({File, Sorted_Functions}, + Analysis#typer_analysis.func), + %% NOTE: However we do not need to sort functions + %% which are imported from included files. + IncFuncMap = typer_map:insert({File, Acc#tmpAcc.incFuncAcc}, + Analysis#typer_analysis.inc_func), + Final_Files = Analysis#typer_analysis.final_files ++ [{File, Module}], + RecordMap = typer_map:insert({File, Records}, Analysis#typer_analysis.record), + Analysis#typer_analysis{final_files=Final_Files, + callgraph=CG, + code_server=CS5, + ex_func=Exported_FuncMap, + inc_func=IncFuncMap, + record=RecordMap, + func=FuncMap}. + +analyze_one_function({Var, FunBody} = Function, Acc) -> + F = cerl:fname_id(Var), + A = cerl:fname_arity(Var), + TmpDialyzerObj = {{Acc#tmpAcc.module, F, A}, Function}, + NewDialyzerObj = Acc#tmpAcc.dialyzerObj ++ [TmpDialyzerObj], + [_, LineNo, {file, FileName}] = cerl:get_ann(FunBody), + BaseName = filename:basename(FileName), + FuncInfo = {LineNo, F, A}, + OriginalName = Acc#tmpAcc.file, + {FuncAcc, IncFuncAcc} = + case (FileName =:= OriginalName) orelse (BaseName =:= OriginalName) of + true -> %% Coming from original file + %% io:format("Added function ~p\n", [{LineNo, F, A}]), + {Acc#tmpAcc.funcAcc ++ [FuncInfo], Acc#tmpAcc.incFuncAcc}; + false -> + %% Coming from other sourses, including: + %% -- .yrl (yecc-generated file) + %% -- yeccpre.hrl (yecc-generated file) + %% -- other cases + {Acc#tmpAcc.funcAcc, Acc#tmpAcc.incFuncAcc ++ [{FileName, FuncInfo}]} + end, + Acc#tmpAcc{funcAcc = FuncAcc, + incFuncAcc = IncFuncAcc, + dialyzerObj = NewDialyzerObj}. + +get_dialyzer_plt(#typer_analysis{plt = PltFile0}) -> + PltFile = + case PltFile0 =:= none of + true -> dialyzer_plt:get_default_plt(); + false -> PltFile0 + end, + dialyzer_plt:from_file(PltFile). diff --git a/lib/typer/src/typer_map.erl b/lib/typer/src/typer_map.erl new file mode 100644 index 0000000000..bf62dea651 --- /dev/null +++ b/lib/typer/src/typer_map.erl @@ -0,0 +1,47 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(typer_map). + +-export([new/0, insert/2, lookup/2, from_list/1, remove/2, fold/3]). + +-spec new() -> dict(). +new() -> + dict:new(). + +-spec insert({term(), term()}, dict()) -> dict(). +insert(Object, Dict) -> + {Key, Value} = Object, + dict:store(Key, Value, Dict). + +-spec lookup(term(), dict()) -> any(). +lookup(Key, Dict) -> + try dict:fetch(Key, Dict) catch error:_ -> none end. + +-spec from_list([{term(), term()}]) -> dict(). +from_list(List) -> + dict:from_list(List). + +-spec remove(term(), dict()) -> dict(). +remove(Key, Dict) -> + dict:erase(Key, Dict). + +-spec fold(fun((term(), term(), term()) -> term()), term(), dict()) -> term(). +fold(Fun, Acc0, Dict) -> + dict:fold(Fun, Acc0, Dict). diff --git a/lib/typer/src/typer_options.erl b/lib/typer/src/typer_options.erl new file mode 100644 index 0000000000..1e53b1b305 --- /dev/null +++ b/lib/typer/src/typer_options.erl @@ -0,0 +1,191 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%%=========================================================================== +%% File : typer_options.erl +%% Author : Bingwen He <[email protected]> +%% Description : Handles all command-line options given to TypEr +%%=========================================================================== + +-module(typer_options). + +-export([process/0]). + +%%--------------------------------------------------------------------------- + +-include("typer.hrl"). + +%%--------------------------------------------------------------------------- +%% Exported functions +%%--------------------------------------------------------------------------- + +-spec process() -> {#args{}, #typer_analysis{}}. + +process() -> + ArgList = init:get_plain_arguments(), + %% io:format("Args is ~p\n",[Args]), + {Args, Analysis} = analyze_args(ArgList, #args{}, #typer_analysis{}), + %% if the mode has not been set, set it to the default mode (show) + {Args, case Analysis#typer_analysis.mode of + undefined -> Analysis#typer_analysis{mode = ?SHOW}; + Mode when is_atom(Mode) -> Analysis + end}. + +%%--------------------------------------------------------------------------- +%% Internal functions +%%--------------------------------------------------------------------------- + +analyze_args([], Args, Analysis) -> + {Args, Analysis}; +analyze_args(ArgList, Args, Analysis) -> + {Result, Rest} = cl(ArgList), + {NewArgs, NewAnalysis} = analyze_result(Result, Args, Analysis), + analyze_args(Rest, NewArgs, NewAnalysis). + +cl(["-h"|_]) -> help_message(); +cl(["--help"|_]) -> help_message(); +cl(["-v"|_]) -> version_message(); +cl(["--version"|_]) -> version_message(); +cl(["--comments"|Opts]) -> {comments, Opts}; +cl(["--show"|Opts]) -> {{mode, ?SHOW}, Opts}; +cl(["--show_exported"|Opts]) -> {{mode, ?SHOW_EXPORTED}, Opts}; +cl(["--show-exported"|Opts]) -> {{mode, ?SHOW_EXPORTED}, Opts}; +cl(["--annotate"|Opts]) -> {{mode, ?ANNOTATE}, Opts}; +cl(["--annotate-inc-files"|Opts]) -> {{mode, ?ANNOTATE_INC_FILES}, Opts}; +cl(["--plt",Plt|Opts]) -> {{plt, Plt}, Opts}; +cl(["-D"++Def|Opts]) -> + case Def of + "" -> typer:error("no variable name specified after -D"); + _ -> + L = re:split(Def, "=", [{return, list}]), + DefPair = process_def_list(L), + {{def, DefPair}, Opts} + end; +cl(["-I",Dir|Opts]) -> {{inc,Dir}, Opts}; +cl(["-I"++Dir|Opts]) -> + case Dir of + "" -> typer:error("no include directory specified after -I"); + _ -> {{inc, Dir}, Opts} + end; +cl(["-T"|Opts]) -> + {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts), + case Files of + [] -> typer:error("no file or directory specified after -T"); + [_|_] -> {{trust, Files}, RestOpts} + end; +cl(["-r"|Opts]) -> + {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts), + {{a_dir_r, Files}, RestOpts}; +cl(["-"++H|_]) -> typer:error("unknown option -"++H); +cl(Opts) -> + {Args, RestOpts} = dialyzer_cl_parse:collect_args(Opts), + {{analyze, Args}, RestOpts}. + +process_def_list(L) -> + case L of + [Name, Value] -> + {ok, Tokens, _} = erl_scan:string(Value ++ "."), + {ok, ErlValue} = erl_parse:parse_term(Tokens), + {list_to_atom(Name), ErlValue}; + [Name] -> + {list_to_atom(Name), true} + end. + +%% Get information about files that the user trusts and wants to analyze +analyze_result({analyze, Val}, Args, Analysis) -> + NewVal = Args#args.analyze ++ Val, + {Args#args{analyze = NewVal}, Analysis}; +analyze_result({a_dir_r, Val}, Args, Analysis) -> + NewVal = Args#args.analyzed_dir_r ++ Val, + {Args#args{analyzed_dir_r = NewVal}, Analysis}; +analyze_result({trust, Val}, Args, Analysis) -> + NewVal = Args#args.trust ++ Val, + {Args#args{trust = NewVal}, Analysis}; +analyze_result(comments, Args, Analysis) -> + {Args, Analysis#typer_analysis{contracts = false}}; +%% Get useful information for actual analysis +analyze_result({mode, Val}, Args, Analysis) -> + case Analysis#typer_analysis.mode of + undefined -> {Args, Analysis#typer_analysis{mode = Val}}; + _ -> mode_error() + end; +analyze_result({def, Val}, Args, Analysis) -> + NewVal = Analysis#typer_analysis.macros ++ [Val], + {Args, Analysis#typer_analysis{macros = NewVal}}; +analyze_result({inc, Val}, Args, Analysis) -> + NewVal = Analysis#typer_analysis.includes ++ [Val], + {Args, Analysis#typer_analysis{includes = NewVal}}; +analyze_result({plt, Plt}, Args, Analysis) -> + {Args, Analysis#typer_analysis{plt = Plt}}. + +%%-------------------------------------------------------------------- + +-spec mode_error() -> no_return(). +mode_error() -> + typer:error("can not do \"show\", \"show-exported\", \"annotate\", and \"annotate-inc-files\" at the same time"). + +-spec version_message() -> no_return(). +version_message() -> + io:format("TypEr version "++?VSN++"\n"), + erlang:halt(0). + +-spec help_message() -> no_return(). +help_message() -> + S = " Usage: typer [--help] [--version] [--comments] [--plt PLT] + [--show | --show-exported | --annotate | --annotate-inc-files] + [-Ddefine]* [-I include_dir]* [-T application]* [-r] file* + + Options: + -r dir* + search directories recursively for .erl files below them + --show + Prints type specifications for all functions on stdout. + (this is the default behaviour; this option is not really needed) + --show-exported (or --show_exported) + Same as --show, but prints specifications for exported functions only + Specs are displayed sorted alphabetically on the function's name + --annotate + Annotates the specified files with type specifications + --annotate-inc-files + Same as --annotate but annotates all -include() files as well as + all .erl files (use this option with caution - has not been tested much) + --comments + Prints type information using Edoc comments, not type specs + --plt PLT + Use the specified dialyzer PLT file rather than the default one + -T file* + The specified file(s) already contain type specifications and these + are to be trusted in order to print specs for the rest of the files + (Multiple files or dirs, separated by spaces, can be specified.) + -Dname (or -Dname=value) + pass the defined name(s) to TypEr + (The syntax of defines is the same as that used by \"erlc\".) + -I include_dir + pass the include_dir to TypEr + (The syntax of includes is the same as that used by \"erlc\".) + --version (or -v) + prints the Typer version and exits + --help (or -h) + prints this message and exits + + Note: + * denotes that multiple occurrences of these options are possible. +", + io:put_chars(S), + erlang:halt(0). diff --git a/lib/typer/src/typer_preprocess.erl b/lib/typer/src/typer_preprocess.erl new file mode 100644 index 0000000000..7cb0b9932b --- /dev/null +++ b/lib/typer/src/typer_preprocess.erl @@ -0,0 +1,154 @@ +%% -*- erlang-indent-level: 2 -*- +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(typer_preprocess). + +-export([get_all_files/2]). + +-include("typer.hrl"). + +%%---------------------------------------------------------------------------- + +-spec get_all_files(#args{}, 'analysis' | 'trust') -> [string()]. + +get_all_files(Args, analysis) -> + case internal_get_all_files(Args#args.analyze, + Args#args.analyzed_dir_r, + fun test_erl_file_exclude_ann/1) of + [] -> typer:error("no file(s) to analyze"); + AllFiles -> AllFiles + end; +get_all_files(Args, trust) -> + internal_get_all_files(Args#args.trust, [], fun test_erl_file/1). + +-spec test_erl_file_exclude_ann(string()) -> boolean(). + +test_erl_file_exclude_ann(File) -> + case filename:extension(File) of + ".erl" -> %% Exclude files ending with ".ann.erl" + case re:run(File, "[\.]ann[\.]erl$") of + {match, _} -> false; + nomatch -> true + end; + _ -> false + end. + +-spec test_erl_file(string()) -> boolean(). + +test_erl_file(File) -> + filename:extension(File) =:= ".erl". + +-spec internal_get_all_files([string()], [string()], + fun((string()) -> boolean())) -> [string()]. + +internal_get_all_files(File_Dir, Dir_R, Fun) -> + All_File_1 = process_file_and_dir(File_Dir, Fun), + All_File_2 = process_dir_recursively(Dir_R, Fun), + remove_dup(All_File_1 ++ All_File_2). + +-spec process_file_and_dir([string()], + fun((string()) -> boolean())) -> [string()]. + +process_file_and_dir(File_Dir, TestFun) -> + Fun = + fun (Elem, Acc) -> + case filelib:is_regular(Elem) of + true -> process_file(Elem, TestFun, Acc); + false -> check_dir(Elem, non_recursive, Acc, TestFun) + end + end, + lists:foldl(Fun, [], File_Dir). + +-spec process_dir_recursively([string()], + fun((string()) -> boolean())) -> [string()]. + +process_dir_recursively(Dirs, TestFun) -> + Fun = fun (Dir, Acc) -> + check_dir(Dir, recursive, Acc, TestFun) + end, + lists:foldl(Fun, [], Dirs). + +-spec check_dir(string(), + 'non_recursive' | 'recursive', + [string()], + fun((string()) -> boolean())) -> [string()]. + +check_dir(Dir, Mode, Acc, Fun) -> + case file:list_dir(Dir) of + {ok, Files} -> + {TmpDirs, TmpFiles} = split_dirs_and_files(Files, Dir), + case Mode of + non_recursive -> + FinalFiles = process_file_and_dir(TmpFiles, Fun), + Acc ++ FinalFiles; + recursive -> + TmpAcc1 = process_file_and_dir(TmpFiles, Fun), + TmpAcc2 = process_dir_recursively(TmpDirs, Fun), + Acc ++ TmpAcc1 ++ TmpAcc2 + end; + {error, eacces} -> + typer:error("no access permission to dir \""++Dir++"\""); + {error, enoent} -> + typer:error("cannot access "++Dir++": No such file or directory"); + {error, _Reason} -> + typer:error("error involving a use of file:list_dir/1") + end. + +%% Same order as the input list +-spec process_file(string(), fun((string()) -> boolean()), string()) -> [string()]. + +process_file(File, TestFun, Acc) -> + case TestFun(File) of + true -> Acc ++ [File]; + false -> Acc + end. + +%% Same order as the input list +-spec split_dirs_and_files([string()], string()) -> {[string()], [string()]}. + +split_dirs_and_files(Elems, Dir) -> + Test_Fun = + fun (Elem, {DirAcc, FileAcc}) -> + File = filename:join(Dir, Elem), + case filelib:is_regular(File) of + false -> {[File|DirAcc], FileAcc}; + true -> {DirAcc, [File|FileAcc]} + end + end, + {Dirs, Files} = lists:foldl(Test_Fun, {[], []}, Elems), + {lists:reverse(Dirs), lists:reverse(Files)}. + +%%----------------------------------------------------------------------- +%% Utilities +%%----------------------------------------------------------------------- + +%% Removes duplicate filenames but it keeps the order of the input list + +-spec remove_dup([string()]) -> [string()]. + +remove_dup(Files) -> + Test_Dup = fun (File, Acc) -> + case lists:member(File, Acc) of + true -> Acc; + false -> [File|Acc] + end + end, + Reversed_Elems = lists:foldl(Test_Dup, [], Files), + lists:reverse(Reversed_Elems). diff --git a/lib/typer/vsn.mk b/lib/typer/vsn.mk new file mode 100644 index 0000000000..9558412375 --- /dev/null +++ b/lib/typer/vsn.mk @@ -0,0 +1 @@ +TYPER_VSN = 0.1.7.3 |