aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib')
-rw-r--r--lib/stdlib/doc/src/dets.xml4
-rw-r--r--lib/stdlib/doc/src/ets.xml141
-rw-r--r--lib/stdlib/doc/src/filename.xml2
-rw-r--r--lib/stdlib/doc/src/io_protocol.xml2
-rw-r--r--lib/stdlib/doc/src/lists.xml2
-rw-r--r--lib/stdlib/doc/src/sofs.xml2
-rw-r--r--lib/stdlib/src/ms_transform.erl193
-rw-r--r--lib/stdlib/src/otp_internal.erl6
-rw-r--r--lib/stdlib/test/ets_SUITE.erl121
-rw-r--r--lib/stdlib/test/ms_transform_SUITE.erl101
-rw-r--r--lib/stdlib/vsn.mk19
11 files changed, 486 insertions, 107 deletions
diff --git a/lib/stdlib/doc/src/dets.xml b/lib/stdlib/doc/src/dets.xml
index 8d1398d3b7..ad100d2cf5 100644
--- a/lib/stdlib/doc/src/dets.xml
+++ b/lib/stdlib/doc/src/dets.xml
@@ -109,7 +109,7 @@ bool() = true | false
file() = string()
int() = integer() >= 0
keypos() = integer() >= 1
-name() = atom() | ref()
+name() = atom() | reference()
no_slots() = integer() >= 0 | default
object() = tuple()
object_cont() = tuple()
@@ -759,7 +759,7 @@ ok
<fsummary>Open an existing Dets table.</fsummary>
<type>
<v>FileName = file()</v>
- <v>Reference = ref()</v>
+ <v>Reference = reference()</v>
</type>
<desc>
<p>Opens an existing table. If the table has not been properly
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index ee1befc882..dd4a289c61 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -1039,15 +1039,22 @@ ets:select(Table,MatchSpec),</code>
the owner terminates.</p>
</item>
<item>
+ <marker id="new_2_write_concurrency"></marker>
<p><c>{write_concurrency,bool()}</c>
- Performance tuning. Default is <c>false</c>, which means that the table
- is optimized towards concurrent read access. An operation that
+ Performance tuning. Default is <c>false</c>. An operation that
mutates (writes to) the table will obtain exclusive access,
blocking any concurrent access of the same table until finished.
If set to <c>true</c>, the table is optimized towards concurrent
write access. Different objects of the same table can be mutated
(and read) by concurrent processes. This is achieved to some degree
at the expense of single access and concurrent reader performance.
+ The <c>write_concurrency</c> option can be combined with the
+ <seealso marker="#new_2_read_concurrency">read_concurrency</seealso>
+ option. You typically want to combine these when large concurrent
+ read bursts and large concurrent write bursts are common (see the
+ documentation of the
+ <seealso marker="#new_2_read_concurrency">read_concurrency</seealso>
+ option for more information).
Note that this option does not change any guarantees about
<seealso marker="#concurrency">atomicy and isolation</seealso>.
Functions that makes such promises over several objects (like
@@ -1055,6 +1062,29 @@ ets:select(Table,MatchSpec),</code>
<p>Table type <c>ordered_set</c> is not affected by this option in current
implementation.</p>
</item>
+ <item>
+ <marker id="new_2_read_concurrency"></marker>
+ <p><c>{read_concurrency,bool()}</c>
+ Performance tuning. Default is <c>false</c>. When set to
+ <c>true</c>, the table is optimized for concurrent read
+ operations. When this option is enabled on a runtime system with
+ SMP support, read operations become much cheaper; especially on
+ systems with multiple physical processors. However, switching
+ between read and write operations becomes more expensive. You
+ typically want to enable this option when concurrent read
+ operations are much more frequent than write operations, or when
+ concurrent reads and writes comes in large read and write
+ bursts (i.e., lots of reads not interrupted by writes, and lots
+ of writes not interrupted by reads). You typically do
+ <em>not</em> want to enable this option when the common access
+ pattern is a few read operations interleaved with a few write
+ operations repeatedly. In this case you will get a performance
+ degradation by enabling this option. The <c>read_concurrency</c>
+ option can be combined with the
+ <seealso marker="#new_2_write_concurrency">write_concurrency</seealso>
+ option. You typically want to combine these when large concurrent
+ read bursts and large concurrent write bursts are common.</p>
+ </item>
</list>
</desc>
</func>
@@ -1355,6 +1385,28 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
</desc>
</func>
<func>
+ <name>select_count(Tab, MatchSpec) -> NumMatched</name>
+ <fsummary>Match the objects in an ETS table against a match_spec and returns the number of objects for which the match_spec returned 'true'</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Object = tuple()</v>
+ <v>MatchSpec = match_spec()</v>
+ <v>NumMatched = integer()</v>
+ </type>
+ <desc>
+ <p>Matches the objects in the table <c>Tab</c> using a
+ <seealso marker="#match_spec">match_spec</seealso>. If the
+ match_spec returns <c>true</c> for an object, that object
+ considered a match and is counted. For any other result from
+ the match_spec the object is not considered a match and is
+ therefore not counted.</p>
+ <p>The function could be described as a <c>match_delete/2</c>
+ that does not actually delete any elements, but only counts
+ them.</p>
+ <p>The function returns the number of objects matched.</p>
+ </desc>
+ </func>
+ <func>
<name>select_delete(Tab, MatchSpec) -> NumDeleted</name>
<fsummary>Match the objects in an ETS table against a match_spec and deletes objects where the match_spec returns 'true'</fsummary>
<type>
@@ -1381,25 +1433,82 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code>
</desc>
</func>
<func>
- <name>select_count(Tab, MatchSpec) -> NumMatched</name>
- <fsummary>Match the objects in an ETS table against a match_spec and returns the number of objects for which the match_spec returned 'true'</fsummary>
+ <name>select_reverse(Tab, MatchSpec) -> [Match]</name>
+ <fsummary>Match the objects in an ETS table against a match_spec.</fsummary>
<type>
<v>Tab = tid() | atom()</v>
- <v>Object = tuple()</v>
+ <v>Match = term()</v>
<v>MatchSpec = match_spec()</v>
- <v>NumMatched = integer()</v>
</type>
<desc>
- <p>Matches the objects in the table <c>Tab</c> using a
- <seealso marker="#match_spec">match_spec</seealso>. If the
- match_spec returns <c>true</c> for an object, that object
- considered a match and is counted. For any other result from
- the match_spec the object is not considered a match and is
- therefore not counted.</p>
- <p>The function could be described as a <c>match_delete/2</c>
- that does not actually delete any elements, but only counts
- them.</p>
- <p>The function returns the number of objects matched.</p>
+
+ <p>Works like <c>select/2</c>, but returns the list in reverse
+ order for the <c>ordered_set</c> table type. For all other table
+ types, the return value is identical to that of <c>select/2</c>.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>select_reverse(Tab, MatchSpec, Limit) -> {[Match],Continuation} | '$end_of_table'</name>
+ <fsummary>Match the objects in an ETS table against a match_spec and returns part of the answers.</fsummary>
+ <type>
+ <v>Tab = tid() | atom()</v>
+ <v>Match = term()</v>
+ <v>MatchSpec = match_spec()</v>
+ <v>Continuation = term()</v>
+ </type>
+ <desc>
+
+ <p>Works like <c>select/3</c>, but for the <c>ordered_set</c>
+ table type, traversing is done starting at the last object in
+ Erlang term order and moves towards the first. For all other
+ table types, the return value is identical to that of
+ <c>select/3</c>.</p>
+
+ <p>Note that this is <em>not</em> equivalent to
+ reversing the result list of a <c>select/3</c> call, as the result list
+ is not only reversed, but also contains the last <c>Limit</c>
+ matching objects in the table, not the first.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>select_reverse(Continuation) -> {[Match],Continuation} | '$end_of_table'</name>
+ <fsummary>Continue matching objects in an ETS table.</fsummary>
+ <type>
+ <v>Match = term()</v>
+ <v>Continuation = term()</v>
+ </type>
+ <desc>
+
+ <p>Continues a match started with
+ <c>ets:select_reverse/3</c>. If the table is an
+ <c>ordered_set</c>, the traversal of the table will continue
+ towards objects with keys earlier in the Erlang term order. The
+ returned list will also contain objects with keys in reverse
+ order.</p>
+
+ <p>For all other table types, the behaviour is exatly that of <c>select/1</c>.</p>
+ <p>Example:</p>
+ <code>
+1> T = ets:new(x,[ordered_set]).
+2> [ ets:insert(T,{N}) || N &lt;- lists:seq(1,10) ].
+...
+3> {R0,C0} = ets:select_reverse(T,[{'_',[],['$_']}],4).
+...
+4> R0.
+[{10},{9},{8},{7}]
+5> {R1,C1} = ets:select_reverse(C0).
+...
+6> R1.
+[{6},{5},{4},{3}]
+7> {R2,C2} = ets:select_reverse(C1).
+...
+8> R2.
+[{2},{1}]
+9> '$end_of_table' = ets:select_reverse(C2).
+...
+ </code>
</desc>
</func>
<func>
diff --git a/lib/stdlib/doc/src/filename.xml b/lib/stdlib/doc/src/filename.xml
index 0cf82fa48b..fe6c6f898e 100644
--- a/lib/stdlib/doc/src/filename.xml
+++ b/lib/stdlib/doc/src/filename.xml
@@ -49,7 +49,7 @@
<title>DATA TYPES</title>
<code type="none">
name() = string() | atom() | DeepList
- DeepList = [char() | atom() | DeepList]</code>
+DeepList = [char() | atom() | DeepList]</code>
</section>
<funcs>
<func>
diff --git a/lib/stdlib/doc/src/io_protocol.xml b/lib/stdlib/doc/src/io_protocol.xml
index b52e862a5c..a97d996d98 100644
--- a/lib/stdlib/doc/src/io_protocol.xml
+++ b/lib/stdlib/doc/src/io_protocol.xml
@@ -79,7 +79,7 @@ sends the reply to.</item>
io_reply. The io-module in the Erlang standard library simply uses the pid()
of the io_server as the ReplyAs datum, but a more complicated client
could have several outstanding io-requests to the same server and
-would then use i.e. a ref() or something else to differentiate among
+would then use i.e. a reference() or something else to differentiate among
the incoming io_reply's. The ReplyAs element should be considered
opaque by the io_server. Note that the pid() of the server is not
explicitly present in the io_reply. The reply can be sent from any
diff --git a/lib/stdlib/doc/src/lists.xml b/lib/stdlib/doc/src/lists.xml
index a273a2301f..b3ad7aaf46 100644
--- a/lib/stdlib/doc/src/lists.xml
+++ b/lib/stdlib/doc/src/lists.xml
@@ -48,7 +48,7 @@
<item><p>if x <c>F</c> y and y <c>F</c> x then x = y (<c>F</c>
is antisymmetric);</p>
</item>
- <item><p>if x <c>F</c> y and and y <c>F</c> z then x <c>F</c> z
+ <item><p>if x <c>F</c> y and y <c>F</c> z then x <c>F</c> z
(<c>F</c> is transitive);</p>
</item>
<item><p>x <c>F</c> y or y <c>F</c> x (<c>F</c> is total).</p>
diff --git a/lib/stdlib/doc/src/sofs.xml b/lib/stdlib/doc/src/sofs.xml
index 8c8ae51262..729df1e678 100644
--- a/lib/stdlib/doc/src/sofs.xml
+++ b/lib/stdlib/doc/src/sofs.xml
@@ -210,7 +210,7 @@
X[i] to Y[i] and S a subset of
X[1]&nbsp;&times;&nbsp;...&nbsp;&times;&nbsp;X[n].
The <marker id="multiple_relative_product"></marker><em>multiple
- relative product</em> of TR and and S is defined to be the
+ relative product</em> of TR and S is defined to be the
set {z&nbsp;: z&nbsp;= ((x[1],&nbsp;...,&nbsp;x[n]), (y[1],...,y[n]))
for some (x[1],&nbsp;...,&nbsp;x[n])&nbsp;in&nbsp;S and for some
(x[i],&nbsp;y[i]) in R[i],
diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl
index 78b1de6e16..a249dea525 100644
--- a/lib/stdlib/src/ms_transform.erl
+++ b/lib/stdlib/src/ms_transform.erl
@@ -43,6 +43,7 @@
-define(ERR_GENREMOTECALL,22).
-define(ERR_GENBINCONSTRUCT,23).
-define(ERR_GENDISALLOWEDOP,24).
+-define(WARN_SHADOW_VAR,50).
-define(ERR_GUARDMATCH,?ERR_GENMATCH+?ERROR_BASE_GUARD).
-define(ERR_BODYMATCH,?ERR_GENMATCH+?ERROR_BASE_BODY).
-define(ERR_GUARDLOCALCALL,?ERR_GENLOCALCALL+?ERROR_BASE_GUARD).
@@ -63,8 +64,13 @@
-define(ERR_BODYDISALLOWEDOP,?ERR_GENDISALLOWEDOP+?ERROR_BASE_BODY).
%%
-%% Called by compiler or ets/dbg:fun2ms when errors occur
+%% Called by compiler or ets/dbg:fun2ms when errors/warnings occur
%%
+format_error({?WARN_SHADOW_VAR,Name}) ->
+ lists:flatten(
+ io_lib:format("variable ~p shadowed in ms_transform fun head",
+ [Name]));
+
format_error(?ERR_NOFUN) ->
"Parameter of ets/dbg:fun2ms/1 is not a literal fun";
format_error(?ERR_ETS_HEAD) ->
@@ -182,7 +188,7 @@ format_error(Else) ->
%%
transform_from_shell(Dialect, Clauses, BoundEnvironment) ->
SaveFilename = setup_filename(),
- case catch ms_clause_list(1,Clauses,Dialect) of
+ case catch ms_clause_list(1,Clauses,Dialect,gb_sets:new()) of
{'EXIT',Reason} ->
cleanup_filename(SaveFilename),
exit(Reason);
@@ -207,6 +213,7 @@ transform_from_shell(Dialect, Clauses, BoundEnvironment) ->
%%
parse_transform(Forms, _Options) ->
SaveFilename = setup_filename(),
+ %io:format("Forms: ~p~n",[Forms]),
case catch forms(Forms) of
{'EXIT',Reason} ->
cleanup_filename(SaveFilename),
@@ -215,12 +222,31 @@ parse_transform(Forms, _Options) ->
{error, [{cleanup_filename(SaveFilename),
[{Line, ?MODULE, R}]}], []};
Else ->
- cleanup_filename(SaveFilename),
+ %io:format("Transformed into: ~p~n",[Else]),
+ case get_warnings() of
+ [] ->
+ cleanup_filename(SaveFilename),
+ Else;
+ WL ->
+ FName = cleanup_filename(SaveFilename) ,
+ WList = [ {FName, [{L, ?MODULE, R}]} || {L,R} <- WL ],
+ {warning, Else, WList}
+ end
+ end.
+
+get_warnings() ->
+ case get(warnings) of
+ undefined ->
+ [];
+ Else ->
Else
end.
+add_warning(Line,R) ->
+ put(warnings,[{Line,R}| get_warnings()]).
+
setup_filename() ->
- {erase(filename),erase(records)}.
+ {erase(filename),erase(records),erase(warnings)}.
put_filename(Name) ->
put(filename,Name).
@@ -235,7 +261,7 @@ get_records() ->
Else ->
Else
end.
-cleanup_filename({Old,OldRec}) ->
+cleanup_filename({Old,OldRec,OldWarnings}) ->
Ret = case erase(filename) of
undefined ->
"TOP_LEVEL";
@@ -248,6 +274,12 @@ cleanup_filename({Old,OldRec}) ->
Rec ->
put(records,Rec)
end,
+ case OldWarnings of
+ undefined ->
+ erase(warnings);
+ Warn ->
+ put(warnings,Warn)
+ end,
case Old of
undefined ->
Ret;
@@ -285,42 +317,77 @@ form({function,Line,Name0,Arity0,Clauses0}) ->
form(AnyOther) ->
AnyOther.
function(Name, Arity, Clauses0) ->
- Clauses1 = clauses(Clauses0),
+ {Clauses1,_} = clauses(Clauses0,gb_sets:new()),
{Name,Arity,Clauses1}.
-clauses([C0|Cs]) ->
- C1 = clause(C0),
- [C1|clauses(Cs)];
-clauses([]) -> [].
-clause({clause,Line,H0,G0,B0}) ->
- B1 = copy(B0),
- {clause,Line,H0,G0,B1}.
+clauses([C0|Cs],Bound) ->
+ {C1,Bound1} = clause(C0,Bound),
+ {C2,Bound2} = clauses(Cs,Bound1),
+ {[C1|C2],Bound2};
+clauses([],Bound) -> {[],Bound}.
+clause({clause,Line,H0,G0,B0},Bound) ->
+ {H1,Bound1} = copy(H0,Bound),
+ {B1,Bound2} = copy(B0,Bound1),
+ {{clause,Line,H1,G0,B1},Bound2}.
copy({call,Line,{remote,_Line2,{atom,_Line3,ets},{atom,_Line4,fun2ms}},
- As0}) ->
- transform_call(ets,Line,As0);
+ As0},Bound) ->
+ {transform_call(ets,Line,As0,Bound),Bound};
copy({call,Line,{remote,_Line2,{record_field,_Line3,
{atom,_Line4,''},{atom,_Line5,ets}},
- {atom,_Line6,fun2ms}}, As0}) ->
+ {atom,_Line6,fun2ms}}, As0},Bound) ->
%% Packages...
- transform_call(ets,Line,As0);
+ {transform_call(ets,Line,As0,Bound),Bound};
copy({call,Line,{remote,_Line2,{atom,_Line3,dbg},{atom,_Line4,fun2ms}},
- As0}) ->
- transform_call(dbg,Line,As0);
-copy(T) when is_tuple(T) ->
- list_to_tuple(copy_list(tuple_to_list(T)));
-copy(L) when is_list(L) ->
- copy_list(L);
-copy(AnyOther) ->
- AnyOther.
+ As0},Bound) ->
+ {transform_call(dbg,Line,As0,Bound),Bound};
+copy({match,Line,A,B},Bound) ->
+ {B1,Bound1} = copy(B,Bound),
+ {A1,Bound2} = copy(A,Bound),
+ {{match,Line,A1,B1},gb_sets:union(Bound1,Bound2)};
+copy({var,_Line,'_'} = VarDef,Bound) ->
+ {VarDef,Bound};
+copy({var,_Line,Name} = VarDef,Bound) ->
+ Bound1 = gb_sets:add(Name,Bound),
+ {VarDef,Bound1};
+copy({'fun',Line,{clauses,Clauses}},Bound) -> % Dont export bindings from funs
+ {NewClauses,_IgnoredBindings} = copy_list(Clauses,Bound),
+ {{'fun',Line,{clauses,NewClauses}},Bound};
+copy({'case',Line,Of,ClausesList},Bound) -> % Dont export bindings from funs
+ {NewOf,NewBind0} = copy(Of,Bound),
+ {NewClausesList,NewBindings} = copy_case_clauses(ClausesList,NewBind0,[]),
+ {{'case',Line,NewOf,NewClausesList},NewBindings};
+copy(T,Bound) when is_tuple(T) ->
+ {L,Bound1} = copy_list(tuple_to_list(T),Bound),
+ {list_to_tuple(L),Bound1};
+copy(L,Bound) when is_list(L) ->
+ copy_list(L,Bound);
+copy(AnyOther,Bound) ->
+ {AnyOther,Bound}.
-copy_list([H|T]) ->
- [copy(H)|copy_list(T)];
-copy_list([]) ->
- [].
+copy_case_clauses([],Bound,AddSets) ->
+ ReallyAdded = gb_sets:intersection(AddSets),
+ {[],gb_sets:union(Bound,ReallyAdded)};
+copy_case_clauses([{clause,Line,Match,Guard,Clauses}|T],Bound,AddSets) ->
+ {NewMatch,MatchBinds} = copy(Match,Bound),
+ {NewGuard,GuardBinds} = copy(Guard,MatchBinds), %% Really no new binds
+ {NewClauses,AllBinds} = copy(Clauses,GuardBinds),
+ %% To limit the setsizes, I subtract what I had before the case clause
+ %% and add it in the end
+ AddedBinds = gb_sets:subtract(AllBinds,Bound),
+ {NewTail,ExportedBindings} =
+ copy_case_clauses(T,Bound,[AddedBinds | AddSets]),
+ {[{clause,Line,NewMatch,NewGuard,NewClauses}|NewTail],ExportedBindings}.
-transform_call(Type,_Line,[{'fun',Line2,{clauses, ClauseList}}]) ->
- ms_clause_list(Line2, ClauseList,Type);
-transform_call(_Type,Line,_NoAbstractFun) ->
+copy_list([H|T],Bound) ->
+ {C1,Bound1} = copy(H,Bound),
+ {C2,Bound2} = copy_list(T,Bound1),
+ {[C1|C2],Bound2};
+copy_list([],Bound) ->
+ {[],Bound}.
+
+transform_call(Type,_Line,[{'fun',Line2,{clauses, ClauseList}}],Bound) ->
+ ms_clause_list(Line2, ClauseList,Type,Bound);
+transform_call(_Type,Line,_NoAbstractFun,_) ->
throw({error,Line,?ERR_NOFUN}).
% Fixup semicolons in guards
@@ -329,18 +396,19 @@ ms_clause_expand({clause, Line, Parameters, Guard = [_,_|_], Body}) ->
ms_clause_expand(_Other) ->
false.
-ms_clause_list(Line,[H|T],Type) ->
+ms_clause_list(Line,[H|T],Type,Bound) ->
case ms_clause_expand(H) of
NewHead when is_list(NewHead) ->
- ms_clause_list(Line,NewHead ++ T, Type);
+ ms_clause_list(Line,NewHead ++ T, Type, Bound);
false ->
- {cons, Line, ms_clause(H,Type), ms_clause_list(Line, T,Type)}
+ {cons, Line, ms_clause(H, Type, Bound),
+ ms_clause_list(Line, T, Type, Bound)}
end;
-ms_clause_list(Line,[],_) ->
+ms_clause_list(Line,[],_,_) ->
{nil,Line}.
-ms_clause({clause, Line, Parameters, Guards, Body},Type) ->
+ms_clause({clause, Line, Parameters, Guards, Body},Type,Bound) ->
check_type(Line,Parameters,Type),
- {MSHead,Bindings} = transform_head(Parameters),
+ {MSHead,Bindings} = transform_head(Parameters,Bound),
MSGuards = transform_guards(Line, Guards, Bindings),
MSBody = transform_body(Line,Body,Bindings),
{tuple, Line, [MSHead,MSGuards,MSBody]}.
@@ -627,29 +695,31 @@ tg(Other,B) ->
Element = io_lib:format("unknown element ~w", [Other]),
throw({error,unknown,{?ERR_GENELEMENT+B#tgd.eb,Element}}).
-transform_head([V]) ->
+transform_head([V],OuterBound) ->
Bind = cre_bind(),
- {NewV,NewBind} = toplevel_head_match(V,Bind),
- th(NewV,NewBind).
+ {NewV,NewBind} = toplevel_head_match(V,Bind,OuterBound),
+ th(NewV,NewBind,OuterBound).
-toplevel_head_match({match,_,{var,_,VName},Expr},B) ->
+toplevel_head_match({match,Line,{var,_,VName},Expr},B,OB) ->
+ warn_var_clash(Line,VName,OB),
{Expr,new_bind({VName,'$_'},B)};
-toplevel_head_match({match,_,Expr,{var,_,VName}},B) ->
+toplevel_head_match({match,Line,Expr,{var,_,VName}},B,OB) ->
+ warn_var_clash(Line,VName,OB),
{Expr,new_bind({VName,'$_'},B)};
-toplevel_head_match(Other,B) ->
+toplevel_head_match(Other,B,_OB) ->
{Other,B}.
-th({record,Line,RName,RFields},B) ->
+th({record,Line,RName,RFields},B,OB) ->
% youch...
RDefs = get_records(),
{KeyList0,NewB} = lists:foldl(fun({record_field,_,{atom,_,Key},Value},
{L,B0}) ->
- {NV,B1} = th(Value,B0),
+ {NV,B1} = th(Value,B0,OB),
{[{Key,NV}|L],B1};
({record_field,_,{var,_,'_'},Value},
{L,B0}) ->
- {NV,B1} = th(Value,B0),
+ {NV,B1} = th(Value,B0,OB),
{[{{default},NV}|L],B1};
(_,_) ->
throw({error,Line,{?ERR_HEADBADREC,
@@ -692,9 +762,9 @@ th({record,Line,RName,RFields},B) ->
_ ->
throw({error,Line,{?ERR_HEADBADREC,RName}})
end;
-th({match,Line,_,_},_) ->
+th({match,Line,_,_},_,_) ->
throw({error,Line,?ERR_HEADMATCH});
-th({atom,Line,A},B) ->
+th({atom,Line,A},B,_OB) ->
case atom_to_list(A) of
[$$|NL] ->
case (catch list_to_integer(NL)) of
@@ -706,10 +776,11 @@ th({atom,Line,A},B) ->
_ ->
{{atom,Line,A},B}
end;
-th({bin_element,_Line0,{var, Line, A},_,_},_) ->
+th({bin_element,_Line0,{var, Line, A},_,_},_,_) ->
throw({error,Line,{?ERR_HEADBINMATCH,A}});
-th({var,Line,Name},B) ->
+th({var,Line,Name},B,OB) ->
+ warn_var_clash(Line,Name,OB),
case lkup_bind(Name,B) of
undefined ->
NewB = new_bind(Name,B),
@@ -717,16 +788,24 @@ th({var,Line,Name},B) ->
Trans ->
{{atom,Line,Trans},B}
end;
-th([H|T],B) ->
- {NH,NB} = th(H,B),
- {NT,NNB} = th(T,NB),
+th([H|T],B,OB) ->
+ {NH,NB} = th(H,B,OB),
+ {NT,NNB} = th(T,NB,OB),
{[NH|NT],NNB};
-th(T,B) when is_tuple(T) ->
- {L,NB} = th(tuple_to_list(T),B),
+th(T,B,OB) when is_tuple(T) ->
+ {L,NB} = th(tuple_to_list(T),B,OB),
{list_to_tuple(L),NB};
-th(Nonstruct,B) ->
+th(Nonstruct,B,_OB) ->
{Nonstruct,B}.
+warn_var_clash(Line,Name,OuterBound) ->
+ case gb_sets:is_member(Name,OuterBound) of
+ true ->
+ add_warning(Line,{?WARN_SHADOW_VAR,Name});
+ _ ->
+ ok
+ end.
+
%% Could be more efficient...
check_multi_field(_, _, [], _) ->
ok;
diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl
index 1514414e48..5c52dfcbf0 100644
--- a/lib/stdlib/src/otp_internal.erl
+++ b/lib/stdlib/src/otp_internal.erl
@@ -361,6 +361,12 @@ obsolete_1(erlang, concat_binary, 1) ->
obsolete_1(ssl, peercert, 2) ->
{deprecated,"deprecated (will be removed in R15A); use ssl:peercert/1 and public_key:pkix_decode_cert/2 instead"};
+%% Added in R14B.
+obsolete_1(public_key, pem_to_der, 1) ->
+ {deprecated,"deprecated (will be removed in R15A); use file:read_file/1 and public_key:pem_decode/1"};
+obsolete_1(public_key, decode_private_key, A) when A =:= 1; A =:= 2 ->
+ {deprecated,{public_key,pem_entry_decode,1},"R15A"};
+
obsolete_1(_, _, _) ->
no.
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
index 5f26b7d431..7f39dbe21f 100644
--- a/lib/stdlib/test/ets_SUITE.erl
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -33,7 +33,7 @@
-export([misc/1, dups/1, misc1/1, safe_fixtable/1, info/1, tab2list/1]).
-export([files/1, tab2file/1, tab2file2/1, tab2file3/1, tabfile_ext1/1,
tabfile_ext2/1, tabfile_ext3/1, tabfile_ext4/1]).
--export([heavy/1, heavy_lookup/1, heavy_lookup_element/1]).
+-export([heavy/1, heavy_lookup/1, heavy_lookup_element/1, heavy_concurrent/1]).
-export([lookup_element/1, lookup_element_mult/1]).
-export([fold/1]).
-export([foldl_ordered/1, foldr_ordered/1, foldl/1, foldr/1, fold_empty/1]).
@@ -90,9 +90,12 @@
match_delete_do/1, match_delete3_do/1, firstnext_do/1,
slot_do/1, match1_do/1, match2_do/1, match_object_do/1, match_object2_do/1,
misc1_do/1, safe_fixtable_do/1, info_do/1, dups_do/1, heavy_lookup_do/1,
- heavy_lookup_element_do/1, member_do/1, otp_5340_do/1, otp_7665_do/1, meta_wb_do/1
+ heavy_lookup_element_do/1, member_do/1, otp_5340_do/1, otp_7665_do/1, meta_wb_do/1,
+ do_heavy_concurrent/1
]).
+-export([t_select_reverse/1]).
+
-include("test_server.hrl").
init_per_testcase(Case, Config) ->
@@ -127,7 +130,7 @@ all(suite) ->
match_heavy, fold, member,
t_delete_object, t_init_table, t_whitebox,
t_delete_all_objects, t_insert_list, t_test_ms,
- t_select_delete, t_ets_dets, memory,
+ t_select_delete, t_ets_dets, memory, t_select_reverse,
t_bucket_disappears,
select_fail,t_insert_new, t_repair_continuation, otp_5340, otp_6338,
otp_6842_select_1000, otp_7665, otp_8732,
@@ -392,7 +395,7 @@ memory(Config) when is_list(Config) ->
?line erts_debug:set_internal_state(available_internal_state, true),
?line ok = chk_normal_tab_struct_size(),
?line L = [T1,T2,T3,T4] = fill_sets_int(1000),
- ?line XRes1 = adjust_xmem(L, {14862,14072,14072,14078}),
+ ?line XRes1 = adjust_xmem(L, {13862,13072,13072,13078}),
?line Res1 = {?S(T1),?S(T2),?S(T3),?S(T4)},
?line lists:foreach(fun(T) ->
Before = ets:info(T,size),
@@ -403,7 +406,7 @@ memory(Config) when is_list(Config) ->
[Key, ets:info(T,type), Before, ets:info(T,size), Objs])
end,
L),
- ?line XRes2 = adjust_xmem(L, {14851,14062,14052,14058}),
+ ?line XRes2 = adjust_xmem(L, {13852,13063,13054,13060}),
?line Res2 = {?S(T1),?S(T2),?S(T3),?S(T4)},
?line lists:foreach(fun(T) ->
Before = ets:info(T,size),
@@ -414,7 +417,7 @@ memory(Config) when is_list(Config) ->
[Key, ets:info(T,type), Before, ets:info(T,size), Objs])
end,
L),
- ?line XRes3 = adjust_xmem(L, {14840,14052,14032,14038}),
+ ?line XRes3 = adjust_xmem(L, {13842,13054,13036,13042}),
?line Res3 = {?S(T1),?S(T2),?S(T3),?S(T4)},
?line lists:foreach(fun(T) ->
?line ets:delete_all_objects(T)
@@ -787,6 +790,67 @@ t_test_ms(Config) when is_list(Config) ->
?line true = (if is_list(String) -> true; true -> false end),
?line verify_etsmem(EtsMem).
+t_select_reverse(doc) ->
+ ["Test the select reverse BIF's"];
+t_select_reverse(suite) ->
+ [];
+t_select_reverse(Config) when is_list(Config) ->
+ ?line Table = ets:new(xxx, [ordered_set]),
+ ?line filltabint(Table,1000),
+ ?line A = lists:reverse(ets:select(Table,[{{'$1', '_'},
+ [{'>',
+ {'rem',
+ '$1', 5},
+ 2}],
+ ['$_']}])),
+ ?line A = ets:select_reverse(Table,[{{'$1', '_'},
+ [{'>',
+ {'rem',
+ '$1', 5},
+ 2}],
+ ['$_']}]),
+ ?line A = reverse_chunked(Table,[{{'$1', '_'},
+ [{'>',
+ {'rem',
+ '$1', 5},
+ 2}],
+ ['$_']}],3),
+ % A set/bag/duplicate_bag should get the same result regardless
+ % of select or select_reverse
+ ?line Table2 = ets:new(xxx, [set]),
+ ?line filltabint(Table2,1000),
+ ?line Table3 = ets:new(xxx, [bag]),
+ ?line filltabint(Table3,1000),
+ ?line Table4 = ets:new(xxx, [duplicate_bag]),
+ ?line filltabint(Table4,1000),
+ ?line lists:map(fun(Tab) ->
+ B = ets:select(Tab,[{{'$1', '_'},
+ [{'>',
+ {'rem',
+ '$1', 5},
+ 2}],
+ ['$_']}]),
+ B = ets:select_reverse(Tab,[{{'$1', '_'},
+ [{'>',
+ {'rem',
+ '$1', 5},
+ 2}],
+ ['$_']}])
+ end,[Table2, Table3, Table4]),
+ ok.
+
+
+
+reverse_chunked(T,MS,N) ->
+ do_reverse_chunked(ets:select_reverse(T,MS,N),[]).
+
+do_reverse_chunked('$end_of_table',Acc) ->
+ lists:reverse(Acc);
+do_reverse_chunked({L,C},Acc) ->
+ NewAcc = lists:reverse(L)++Acc,
+ do_reverse_chunked(ets:select_reverse(C), NewAcc).
+
+
t_select_delete(doc) ->
["Test the ets:select_delete/2 and ets:select_count/2 BIF's"];
t_select_delete(suite) ->
@@ -3878,7 +3942,7 @@ make_sub_binary(List, Num) when is_list(List) ->
{_,B} = split_binary(Bin, N+1),
B.
-heavy(suite) -> [heavy_lookup, heavy_lookup_element].
+heavy(suite) -> [heavy_lookup, heavy_lookup_element, heavy_concurrent].
%% Lookup stuff like crazy...
heavy_lookup(doc) -> ["Performs multiple lookups for every key ",
@@ -3941,6 +4005,44 @@ do_lookup_element(Tab, N, M) ->
end.
+heavy_concurrent(_Config) ->
+ repeat_for_opts(do_heavy_concurrent).
+
+do_heavy_concurrent(Opts) ->
+ ?line Size = 20000,
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(blupp, [set, public, {keypos, 2} | Opts]),
+ ?line ok = fill_tab2(Tab, 0, Size),
+ ?line Procs = lists:map(
+ fun (N) ->
+ spawn_link(
+ fun () ->
+ do_heavy_concurrent_proc(Tab, Size, N)
+ end)
+ end,
+ lists:seq(1, 500)),
+ ?line lists:foreach(fun (P) ->
+ M = erlang:monitor(process, P),
+ receive
+ {'DOWN', M, process, P, _} ->
+ ok
+ end
+ end,
+ Procs),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+do_heavy_concurrent_proc(_Tab, 0, _Offs) ->
+ done;
+do_heavy_concurrent_proc(Tab, N, Offs) when (N+Offs) rem 100 == 0 ->
+ Data = {"here", are, "S O M E ", data, "toooooooooooooooooo", insert,
+ make_ref(), make_ref(), make_ref()},
+ true=ets:insert(Tab, {{self(),Data}, N}),
+ do_heavy_concurrent_proc(Tab, N-1, Offs);
+do_heavy_concurrent_proc(Tab, N, Offs) ->
+ _ = ets:lookup(Tab, N),
+ do_heavy_concurrent_proc(Tab, N-1, Offs).
+
fold(suite) -> [foldl_ordered, foldr_ordered,
foldl, foldr,
fold_empty].
@@ -5343,7 +5445,7 @@ only_if_smp(Schedulers, Func) ->
%% Repeat test function with different combination of table options
%%
repeat_for_opts(F) ->
- repeat_for_opts(F, [write_concurrency]).
+ repeat_for_opts(F, [write_concurrency, read_concurrency]).
repeat_for_opts(F, OptGenList) when is_atom(F) ->
repeat_for_opts(fun(Opts) -> ?MODULE:F(Opts) end, OptGenList);
@@ -5363,6 +5465,7 @@ repeat_for_opts(F, [Atom | Tail], AccList) when is_atom(Atom) ->
repeat_for_opts(F, [repeat_for_opts_atom2list(Atom) | Tail ], AccList).
repeat_for_opts_atom2list(all_types) -> [set,ordered_set,bag,duplicate_bag];
-repeat_for_opts_atom2list(write_concurrency) -> [{write_concurrency,false},{write_concurrency,true}].
+repeat_for_opts_atom2list(write_concurrency) -> [{write_concurrency,false},{write_concurrency,true}];
+repeat_for_opts_atom2list(read_concurrency) -> [{read_concurrency,false},{read_concurrency,true}].
diff --git a/lib/stdlib/test/ms_transform_SUITE.erl b/lib/stdlib/test/ms_transform_SUITE.erl
index 79a0a9af89..2d90d5b823 100644
--- a/lib/stdlib/test/ms_transform_SUITE.erl
+++ b/lib/stdlib/test/ms_transform_SUITE.erl
@@ -37,6 +37,7 @@
-export([andalso_orelse/1]).
-export([float_1_function/1]).
-export([action_function/1]).
+-export([warnings/1]).
-export([init_per_testcase/2, fin_per_testcase/2]).
init_per_testcase(_Func, Config) ->
@@ -50,8 +51,90 @@ fin_per_testcase(_Func, Config) ->
all(suite) -> [from_shell,basic_ets,basic_dbg,records,record_index,multipass,
bitsyntax, record_defaults, andalso_orelse,
float_1_function, action_function,
+ warnings,
top_match, old_guards, autoimported, semicolon].
+%% This may be subject to change
+-define(WARN_NUMBER_SHADOW,50).
+warnings(suite) ->
+ [];
+warnings(doc) ->
+ ["Check that shadowed variables in fun head generate warning"];
+warnings(Config) when is_list(Config) ->
+ ?line setup(Config),
+ Prog = <<"A=5, "
+ "ets:fun2ms(fun({A,B}) "
+ " when is_integer(A) and (A+5 > B) -> "
+ " A andalso B "
+ " end)">>,
+ ?line [{_,[{_,ms_transform,{?WARN_NUMBER_SHADOW,'A'}}]}] =
+ compile_ww(Prog),
+ Prog2 = <<"C=5, "
+ "ets:fun2ms(fun({A,B} = C) "
+ " when is_integer(A) and (A+5 > B) -> "
+ " {A andalso B,C} "
+ " end)">>,
+ ?line [{_,[{_,ms_transform,{?WARN_NUMBER_SHADOW,'C'}}]}] =
+ compile_ww(Prog2),
+ Rec3 = <<"-record(a,{a,b,c,d=foppa}).">>,
+ Prog3 = <<"A=3,C=5, "
+ "ets:fun2ms(fun(#a{a = A, b = B} = C) "
+ " when is_integer(A) and (A+5 > B) -> "
+ " {A andalso B,C} "
+ " end)">>,
+ ?line [{_,[{_,ms_transform,{?WARN_NUMBER_SHADOW,'A'}},
+ {_,ms_transform,{?WARN_NUMBER_SHADOW,'C'}}]}] =
+ compile_ww(Rec3,Prog3),
+ Rec4 = <<"-record(a,{a,b,c,d=foppa}).">>,
+ Prog4 = <<"A=3,C=5, "
+ "F = fun(B) -> B*3 end,"
+ "erlang:display(F(A)),"
+ "ets:fun2ms(fun(#a{a = A, b = B} = C) "
+ " when is_integer(A) and (A+5 > B) -> "
+ " {A andalso B,C} "
+ " end)">>,
+ ?line [{_,[{_,ms_transform,{?WARN_NUMBER_SHADOW,'A'}},
+ {_,ms_transform,{?WARN_NUMBER_SHADOW,'C'}}]}] =
+ compile_ww(Rec4,Prog4),
+ Rec5 = <<"-record(a,{a,b,c,d=foppa}).">>,
+ Prog5 = <<"A=3,C=5, "
+ "F = fun(B) -> B*3 end,"
+ "erlang:display(F(A)),"
+ "B = ets:fun2ms(fun(#a{a = A, b = B} = C) "
+ " when is_integer(A) and (A+5 > B) -> "
+ " {A andalso B,C} "
+ " end)">>,
+ ?line [{_,[{_,ms_transform,{?WARN_NUMBER_SHADOW,'A'}},
+ {_,ms_transform,{?WARN_NUMBER_SHADOW,'C'}}]}] =
+ compile_ww(Rec5,Prog5),
+ Prog6 = <<" X=bar, "
+ " A = case X of"
+ " foo ->"
+ " foo;"
+ " Y ->"
+ " ets:fun2ms(fun(Y) ->" % This is a warning
+ " 3*Y"
+ " end)"
+ " end,"
+ " ets:fun2ms(fun(Y) ->" % Y out of "scope" here, so no warning
+ " {3*Y,A}"
+ " end)">>,
+ ?line [{_,[{_,ms_transform,{?WARN_NUMBER_SHADOW,'Y'}}]}] =
+ compile_ww(Prog6),
+ Prog7 = <<" X=bar, "
+ " A = case X of"
+ " foo ->"
+ " Y = foo;"
+ " Y ->"
+ " bar"
+ " end,"
+ " ets:fun2ms(fun(Y) ->" % Y exported from case and safe, so warn
+ " {3*Y,A}"
+ " end)">>,
+ ?line [{_,[{_,ms_transform,{?WARN_NUMBER_SHADOW,'Y'}}]}] =
+ compile_ww(Prog7),
+ ok.
+
andalso_orelse(suite) ->
[];
andalso_orelse(doc) ->
@@ -721,6 +804,24 @@ compile_and_run(Records,Expr) ->
code:load_binary(tmp,FN,Bin),
tmp:tmp().
+compile_ww(Expr) ->
+ compile_ww(<<>>,Expr).
+compile_ww(Records,Expr) ->
+ Prog = <<
+ "-module(tmp).\n",
+ "-include_lib(\"stdlib/include/ms_transform.hrl\").\n",
+ "-export([tmp/0]).\n",
+ Records/binary,"\n",
+ "tmp() ->\n",
+ Expr/binary,".\n">>,
+ FN=temp_name(),
+ file:write_file(FN,Prog),
+ {ok,Forms} = epp:parse_file(FN,"",""),
+ {ok,tmp,_Bin,Wlist} = compile:forms(Forms,[return_warnings,
+ nowarn_unused_vars,
+ nowarn_unused_record]),
+ Wlist.
+
do_eval(String) ->
{done,{ok,T,_},[]} = erl_scan:tokens(
[],
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index 02ea6d9d68..1757d35160 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1,20 +1 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2010. 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%
-#
-
STDLIB_VSN = 1.17.1