aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib')
-rw-r--r--lib/stdlib/doc/src/ets.xml23
-rw-r--r--lib/stdlib/doc/src/gen_statem.xml4
-rw-r--r--lib/stdlib/doc/src/lists.xml4
-rw-r--r--lib/stdlib/doc/src/notes.xml470
-rw-r--r--lib/stdlib/doc/src/string.xml8
-rw-r--r--lib/stdlib/doc/src/sys.xml2
-rw-r--r--lib/stdlib/src/c.erl28
-rw-r--r--lib/stdlib/src/epp.erl24
-rw-r--r--lib/stdlib/src/erl_compile.erl4
-rw-r--r--lib/stdlib/src/erl_lint.erl152
-rw-r--r--lib/stdlib/src/erl_parse.yrl9
-rw-r--r--lib/stdlib/src/erl_tar.erl47
-rw-r--r--lib/stdlib/src/erl_tar.hrl8
-rw-r--r--lib/stdlib/src/error_logger_file_h.erl14
-rw-r--r--lib/stdlib/src/error_logger_tty_h.erl14
-rw-r--r--lib/stdlib/src/escript.erl6
-rw-r--r--lib/stdlib/src/gen.erl2
-rw-r--r--lib/stdlib/src/gen_server.erl187
-rw-r--r--lib/stdlib/src/gen_statem.erl8
-rw-r--r--lib/stdlib/src/io_lib.erl115
-rw-r--r--lib/stdlib/src/io_lib_format.erl21
-rw-r--r--lib/stdlib/src/io_lib_pretty.erl2
-rw-r--r--lib/stdlib/src/lib.erl69
-rw-r--r--lib/stdlib/src/ms_transform.erl62
-rw-r--r--lib/stdlib/src/proc_lib.erl114
-rw-r--r--lib/stdlib/src/shell.erl46
-rw-r--r--lib/stdlib/src/string.erl24
-rw-r--r--lib/stdlib/src/sys.erl6
-rw-r--r--lib/stdlib/src/unicode.erl109
-rw-r--r--lib/stdlib/test/c_SUITE.erl53
-rw-r--r--lib/stdlib/test/epp_SUITE.erl29
-rw-r--r--lib/stdlib/test/erl_lint_SUITE.erl138
-rw-r--r--lib/stdlib/test/gen_server_SUITE.erl2
-rw-r--r--lib/stdlib/test/io_SUITE.erl59
-rw-r--r--lib/stdlib/test/ms_transform_SUITE.erl15
-rw-r--r--lib/stdlib/test/proc_lib_SUITE.erl59
-rw-r--r--lib/stdlib/test/shell_SUITE.erl79
-rw-r--r--lib/stdlib/test/string_SUITE.erl4
-rw-r--r--lib/stdlib/test/tar_SUITE.erl41
-rw-r--r--lib/stdlib/test/unicode_SUITE.erl24
-rw-r--r--lib/stdlib/test/unicode_util_SUITE.erl4
-rwxr-xr-xlib/stdlib/uc_spec/gen_unicode_mod.escript49
-rw-r--r--lib/stdlib/vsn.mk2
43 files changed, 1656 insertions, 484 deletions
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index 342be80f98..f6f3d18d6a 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -49,13 +49,22 @@
associated with each key. A <c>bag</c> or <c>duplicate_bag</c> table can
have many objects associated with each key.</p>
- <p>The number of tables stored at one Erlang node is limited.
- The current default limit is about 1400 tables. The upper
- limit can be increased by setting environment variable
- <c>ERL_MAX_ETS_TABLES</c> before starting the Erlang runtime
- system (that is, with option <c>-env</c> to
- <c>erl</c>/<c>werl</c>). The actual limit can be slightly higher
- than the one specified, but never lower.</p>
+ <note>
+ <p>
+ The number of tables stored at one Erlang node <em>used</em> to
+ be limited. This is no longer the case (except by memory usage).
+ The previous default limit was about 1400 tables and
+ could be increased by setting the environment variable
+ <c>ERL_MAX_ETS_TABLES</c> before starting the Erlang runtime
+ system. This hard limit has been removed, but it is currently
+ useful to set the <c>ERL_MAX_ETS_TABLES</c> anyway. It should be
+ set to an approximate of the maximum amount of tables used. This since
+ an internal table for named tables is sized using this value. If
+ large amounts of named tables are used and <c>ERL_MAX_ETS_TABLES</c>
+ hasn't been increased, the performance of named table lookup will
+ degrade.
+ </p>
+ </note>
<p>Notice that there is no automatic garbage collection for tables.
Even if there are no references to a table from any process, it
diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml
index 1aac88c308..a7caa71dcb 100644
--- a/lib/stdlib/doc/src/gen_statem.xml
+++ b/lib/stdlib/doc/src/gen_statem.xml
@@ -346,7 +346,7 @@ ok
<p>
To compare styles, here follows the same example using
<seealso marker="#type-callback_mode"><em>callback mode</em></seealso>
- <c>state_functions</c>, or rather the code to replace
+ <c>handle_event_function</c>, or rather the code to replace
after function <c>init/1</c> of the <c>pushbutton.erl</c>
example file above:
</p>
@@ -1098,7 +1098,7 @@ handle_event(_, _, State, Data) ->
<seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>
or
<seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>
- would be weird on the border of whichcraft
+ would be weird on the border of witchcraft
since there has been no earlier call to a
<seealso marker="#state callback">state callback</seealso>
in this server.
diff --git a/lib/stdlib/doc/src/lists.xml b/lib/stdlib/doc/src/lists.xml
index 60dbae70c2..7efafedc82 100644
--- a/lib/stdlib/doc/src/lists.xml
+++ b/lib/stdlib/doc/src/lists.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2016</year>
+ <year>1996</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -187,7 +187,7 @@
<desc>
<p>Calls <c><anno>Fun</anno>(<anno>Elem</anno>)</c> on successive
elements <c>Elem</c> of <c><anno>List1</anno></c>.
- <c><anno>Fun</anno>/2</c> must return either a Boolean or a tuple
+ <c><anno>Fun</anno>/1</c> must return either a Boolean or a tuple
<c>{true, <anno>Value</anno>}</c>. The function returns the list of
elements for which <c><anno>Fun</anno></c> returns a new value, where
a value of <c>true</c> is synonymous with
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index a8a252cb35..bdd5b39cd3 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -31,6 +31,476 @@
</header>
<p>This document describes the changes made to the STDLIB application.</p>
+<section><title>STDLIB 3.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> A bug in <c>proc_lib:format()</c> introduced in
+ Erlang/OTP 20.0 is corrected. </p>
+ <p>
+ Own Id: OTP-14482 Aux Id: PR-1488 </p>
+ </item>
+ <item>
+ <p>
+ Fix string:len/1 to be compatible with previous versions.</p>
+ <p>
+ Own Id: OTP-14487 Aux Id: ERIERL-40 </p>
+ </item>
+ <item>
+ <p>
+ In OTP-20.0, the behavior of c, make, and ct_make was
+ changed so that in some cases the beam files by default
+ would be written to the directory where the source files
+ were found. This is now changed back to the old behavior
+ so beam files are by default written to current
+ directory.</p>
+ <p>
+ Own Id: OTP-14489 Aux Id: ERL-438 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 3.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>For many releases, it has been legal to override a BIF
+ with a local function having the same name. However,
+ calling a local function with the same name as guard BIF
+ as filter in a list comprehension was not allowed.</p>
+ <p>
+ Own Id: OTP-13690</p>
+ </item>
+ <item>
+ <p> A new (default) pseudo-random number generator
+ algorithm Xoroshiro116+ has been implemented in the
+ <c>rand</c> module. </p><p> The old algorithm
+ implementations had a number of flaws so they are all
+ deprecated, but corrected versions of two of them have
+ been added. See the documentation. </p>
+ <p>
+ Own Id: OTP-14295 Aux Id: PR-1372 </p>
+ </item>
+ <item>
+ <p> The Erlang shell, <c>qlc:string_to_handle()</c>, and
+ the Debugger (the Evaluator area and Edit variable window
+ of the Bindings area) can parse pids, ports, references,
+ and external funs, as long as they can be created in the
+ running system. </p>
+ <p>
+ Own Id: OTP-14296</p>
+ </item>
+ <item>
+ <p>Internal code change: Calls to <c>catch</c> followed
+ by a call to <c>erlang:get_stacktrace/0</c> has been
+ rewritten to use <c>try</c> instead of <c>catch</c> to
+ make the code future-proof.</p>
+ <p>
+ Own Id: OTP-14400</p>
+ </item>
+ <item>
+ <p> The <c>ms_transform</c> module, used by
+ <c>ets:fun2ms/1</c> and <c>dbg:fun2ms/1</c>, evaluates
+ constant arithmetic expressions. This is necessary since
+ the Erlang compiler, which normally evaluates constant
+ expressions, does not recognize the format generated by
+ <c>ms_transform</c>. </p>
+ <p>
+ Own Id: OTP-14454 Aux Id: ERIERL-29 </p>
+ </item>
+ <item>
+ <p> The state machine engine <c>gen_statem</c> can now
+ handle generic time-outs (multiple named) as well as
+ absolute time-out time. See the documentation. </p><p>
+ The <c>gen_statem</c> callback <c>Module:init/1</c> has
+ become mandatory to harmonize with other <c>gen_*</c>
+ modules. This may be an incompatibility for
+ <c>gen_statem</c> callback modules that use
+ <c>gen_statem:enter_loop/4-6</c>. </p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-14531</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Improved unicode support for strings. Added normalization
+ functions in the <c>unicode</c> module. Extended the
+ <c>string</c> module API with new functions with improved
+ unicode handling and that works on grapheme clusters. The
+ new functions operates on the <c>unicode:chardata()</c>
+ type, thus they also accept <c>UTF-8 binaries</c> as
+ input. </p>
+ <p>
+ The old string API have been marked as obsolete. The
+ return values have been changed for some error cases.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-10289 Aux Id: OTP-10309 </p>
+ </item>
+ <item>
+ <p>There are two new guard BIFs '<c>floor/1</c>' and
+ '<c>ceil/1</c>'. They both return integers. In the
+ '<c>math</c>' module, there are two new BIFs with the
+ same names that return floating point values.</p>
+ <p>
+ Own Id: OTP-13692</p>
+ </item>
+ <item>
+ <p>
+ Making code_change, terminate and handle_info callbacks
+ optional in the OTP behaviours.</p>
+ <p>
+ Own Id: OTP-13801</p>
+ </item>
+ <item>
+ <p> The support for Dets files created with Erlang/OTP R7
+ and earlier is removed. </p>
+ <p>
+ Own Id: OTP-13830</p>
+ </item>
+ <item>
+ <p>Replaced usage of deprecated symbolic <seealso
+ marker="erts:erlang#type-time_unit"><c>time
+ unit</c></seealso> representations.</p>
+ <p>
+ Own Id: OTP-13831 Aux Id: OTP-13735 </p>
+ </item>
+ <item>
+ <p>The function <c>fmod/2</c> has been added to the
+ <c>math</c> module.</p>
+ <p>
+ Own Id: OTP-14000</p>
+ </item>
+ <item>
+ <p>The EXIT signals received from processes using
+ <c>proc_lib</c> now looks like EXIT signals from
+ processes that were spawned using <c>spawn_link</c>. In
+ particular, that means that the stack trace is now
+ included in the EXIT signal so that it can see where the
+ process crashed.</p>
+ <p>
+ Own Id: OTP-14001</p>
+ </item>
+ <item>
+ <p><c>sets:add_element/2</c> is faster when adding an
+ element that is already present, and
+ <c>sets:del_element/2</c> is faster when the element to
+ be deleted is not present. This optimization can make
+ certain operations, such as sets:union/2 with many
+ overlapping elements, up to two orders of magnitude
+ faster.</p>
+ <p>
+ Own Id: OTP-14035</p>
+ </item>
+ <item>
+ <p>
+ Add information in doc about supervisor shutdown reason
+ when maximum restart frequency is reached.</p>
+ <p>
+ Own Id: OTP-14037 Aux Id: PR-1233 </p>
+ </item>
+ <item>
+ <p>
+ Added <c>rand:jump/[0|1]</c> functions.</p>
+ <p>
+ Own Id: OTP-14038 Aux Id: PR-1235 </p>
+ </item>
+ <item>
+ <p>Functions for detecting changed code has been added.
+ <c>code:modified_modules/0</c> returns all currently
+ loaded modules that have changed on disk.
+ <c>code:module_status/1</c> returns the status for a
+ module. In the shell and in <c>c</c> module, <c>mm/0</c>
+ is short for <c>code:modified_modules/0</c>, and
+ <c>lm/0</c> reloads all currently loaded modules that
+ have changed on disk.</p>
+ <p>
+ Own Id: OTP-14059</p>
+ </item>
+ <item>
+ <p>Each assert macro in <c>assert.hrl</c> now has a
+ corresponding version with an extra argument, for adding
+ comments to assertions. These can for example be printed
+ as part of error reports, to clarify the meaning of the
+ check that failed.</p>
+ <p>
+ Own Id: OTP-14066</p>
+ </item>
+ <item>
+ <p><c>error_logger_tty_h</c> and
+ <c>error_logger_file_h</c> now inserts the node
+ information for nonlocal messages before the message
+ itself instead of after, both for readability and so as
+ not to change the line termination property at the end of
+ the message.</p>
+ <p>
+ Own Id: OTP-14068</p>
+ </item>
+ <item>
+ <p>The Erlang code linter checks for badly formed type
+ constraints. </p>
+ <p>
+ Own Id: OTP-14070 Aux Id: PR-1214 </p>
+ </item>
+ <item>
+ <p>By default, there will now be a warning when
+ <c>export_all</c> is used. The warning can be disabled
+ using <c>nowarn_export_all</c>.</p>
+ <p>
+ Own Id: OTP-14071</p>
+ </item>
+ <item>
+ <p>When a <c>gen_server</c> process crashes, the
+ stacktrace for the client will be printed to facilitate
+ debugging.</p>
+ <p>
+ Own Id: OTP-14089</p>
+ </item>
+ <item>
+ <p>Optimized ETS operations by changing table identifier
+ type from integer to reference. The reference enables a
+ more direct mapping to the table with less potential lock
+ contention and makes especially creation and deletion of
+ tables scale much better.</p> <p>The change of the opaque
+ type for the ETS table identifiers may cause failure in
+ code that make faulty assumptions about this opaque
+ type.</p> <note> <p> The number of tables stored at one
+ Erlang node <em>used</em> to be limited. This is no
+ longer the case (except by memory usage). The previous
+ default limit was about 1400 tables and could be
+ increased by setting the environment variable
+ <c>ERL_MAX_ETS_TABLES</c> before starting the Erlang
+ runtime system. This hard limit has been removed, but it
+ is currently useful to set the <c>ERL_MAX_ETS_TABLES</c>
+ anyway. It should be set to an approximate of the maximum
+ amount of tables used. This since an internal table for
+ named tables is sized using this value. If large amounts
+ of named tables are used and <c>ERL_MAX_ETS_TABLES</c>
+ hasn't been increased, the performance of named table
+ lookup will degrade. </p> </note>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-14094</p>
+ </item>
+ <item>
+ <p><c>take/2</c> has been added to <c>dict</c>,
+ <c>orddict</c>, and <c>gb_trees</c>. <c>take_any/2</c>
+ has been added to <c>gb_trees</c>.</p>
+ <p>
+ Own Id: OTP-14102</p>
+ </item>
+ <item>
+ <p>
+ Extend gen_event API to handle options as well.</p>
+ <p>
+ Own Id: OTP-14123</p>
+ </item>
+ <item>
+ <p>
+ Advice on how to tune the supervisor restart frequency
+ (intensity and period) is added to System Documentation -
+ Design Principles - Supervisor Behaviour.</p>
+ <p>
+ Own Id: OTP-14168 Aux Id: PR-1289 </p>
+ </item>
+ <item>
+ <p>
+ gen_fsm is deprecated and is replaced by gen_statem,
+ however for backwards compatibility reasons gen_fsm may
+ continue to exist as an undocumented feature for quite
+ some time.</p>
+ <p>
+ Own Id: OTP-14183</p>
+ </item>
+ <item>
+ <p>The shell functions <c>c/1</c> and <c>c/2</c> have
+ been extended so that if the argument is a module name
+ instead of a file name, it automatically locates the
+ .beam file and the corresponding source file, and then
+ recompiles the module using the same compiler options
+ (plus any options passed to c/2). If compilation fails,
+ the old beam file is preserved. Also adds <c>c(Mod, Opts,
+ Filter)</c>, where the Filter argument allows you to
+ remove old compiler options before the new options are
+ added.</p> <p>New utility functions <c>file_find/2/3</c>
+ and <c>find_source/1/2/3</c> have been added to
+ <c>filelib</c>.</p>
+ <p>
+ Own Id: OTP-14190</p>
+ </item>
+ <item>
+ <p><c>erl_tar</c> in previous versions of OTP only
+ supports the USTAR format. That limited path names to at
+ most 255 bytes, and did not support Unicode characters in
+ names in a portable way.</p>
+ <p><c>erl_tar</c> now has support for reading tar
+ archives in the formats currently in common use, such as
+ v7, STAR, USTAR, PAX, and GNU tar's extensions to the
+ STAR/USTAR format. When writing tar archives,
+ <c>erl_tar</c> can now write them in the <c>PAX</c>
+ format if necessary (for example, to support very long
+ filenames or filenames with Unicode characters). If
+ possible, <c>erl_tar</c> will still write tar archives in
+ the USTAR for maximum portability.</p>
+ <p>
+ Own Id: OTP-14226</p>
+ </item>
+ <item>
+ <p><c>base64:mime_decode/1</c> has been optimized so that
+ it is now almost as fast as<c>base64:decode/1</c>; it
+ used be noticeably slower.</p>
+ <p>
+ Own Id: OTP-14245</p>
+ </item>
+ <item>
+ <p><c>erl_tar</c> will now strip any leading '<c>/</c>'
+ from pathnames when extracting files from a tar archive
+ and write a message to the error logger. There is also
+ new check for directory traversal attacks; if a relative
+ path points above the current working directory the
+ extraction will be aborted.</p>
+ <p>
+ Own Id: OTP-14278</p>
+ </item>
+ <item>
+ <p> Miscellaneous updates due to atoms containing
+ arbitrary Unicode characters. </p>
+ <p>
+ Own Id: OTP-14285</p>
+ </item>
+ <item>
+ <p>
+ The Crypto application now supports generation of
+ cryptographically strong random numbers (floats &lt; 1.0
+ and integer arbitrary ranges) as a plugin to the 'rand'
+ module.</p>
+ <p>
+ Own Id: OTP-14317 Aux Id: PR-1372 </p>
+ </item>
+ <item>
+ <p>
+ Add new function <c>ets:select_replace/2</c> which
+ performs atomic "compare-and-swap" operations for ETS
+ objects using match specifications.</p>
+ <p>
+ Own Id: OTP-14319 Aux Id: PR-1076 </p>
+ </item>
+ <item>
+ <p> The Erlang code linter checks for bad <c>dialyzer</c>
+ attributes. It also checks for bad type variables in type
+ declarations. </p>
+ <p>
+ Own Id: OTP-14323</p>
+ </item>
+ <item>
+ <p> Two new functions has been implemented in the
+ <c>rand</c> module; <c>normal/2</c> and
+ <c>normal_s/3</c>, that both produce normal distribution
+ (pseudo) random numbers with mean value and variance
+ according to arguments. </p>
+ <p>
+ Own Id: OTP-14328 Aux Id: PR-1382 </p>
+ </item>
+ <item>
+ <p>
+ Upgraded the OTP internal PCRE library from version 8.33
+ to version 8.40. This library is used for implementation
+ of the <seealso marker="stdlib:re"><c>re</c></seealso>
+ regular expressions module.</p>
+ <p>
+ Besides various bug fixes, the new version allows for
+ better stack protection. In order to utilize this
+ feature, the stack size of normal scheduler threads is
+ now by default set to 128 kilo words on all platforms.
+ The stack size of normal scheduler threads can be set
+ upon system start by passing the <seealso
+ marker="erts:erl#sched_thread_stack_size"><c>+sss</c></seealso>
+ command line argument to the <seealso
+ marker="erts:erl"><c>erl</c></seealso> command.</p>
+ <p>
+ See <url
+ href="http://pcre.org/original/changelog.txt"><c>http://pcre.org/original/changelog.txt</c></url>
+ for information about changes made to PCRE between the
+ versions 8.33 and 8.40.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-14331 Aux Id: ERL-208 </p>
+ </item>
+ <item>
+ <p>
+ Added function <c>re:version/0</c> which returns
+ information about the OTP internal PCRE version used for
+ implementation of the <c>re</c> module.</p>
+ <p>
+ Own Id: OTP-14347 Aux Id: PR-1412 </p>
+ </item>
+ <item>
+ <p>The format of debug information that is stored in BEAM
+ files (when <c>debug_info</c> is used) has been changed.
+ The purpose of the change is to better support other
+ BEAM-based languages such as Elixir or LFE.</p>
+ <p>All tools included in OTP (dialyzer, debugger, cover,
+ and so on) will handle both the new format and the
+ previous format. Tools that retrieve the debug
+ information using <c>beam_lib:chunk(Beam,
+ [abstract_code])</c> will continue to work with both the
+ new and old format. Tools that call
+ <c>beam_lib:chunk(Beam, ["Abst"])</c> will not work with
+ the new format.</p>
+ <p>For more information, see the description of
+ <c>debug_info</c> in the documentation for
+ <c>beam_lib</c> and the description of the
+ <c>{debug_info,{Backend,Data}}</c> option in the
+ documentation for <c>compile</c>.</p>
+ <p>
+ Own Id: OTP-14369 Aux Id: PR-1367 </p>
+ </item>
+ <item>
+ <p>
+ Add option hibernate_after to gen_server, gen_statem and
+ gen_event. Also added to the deprecated gen_fsm
+ behaviour.</p>
+ <p>
+ Own Id: OTP-14405</p>
+ </item>
+ <item>
+ <p> The size of crash reports created by
+ <c>gen_server</c>, <c>gen_statem</c> and <c>proc_lib</c>
+ is limited with aid of the Kernel application variable
+ <c>error_logger_format_depth</c>. The purpose is to limit
+ the size of the messages sent to the <c>error_logger</c>
+ process when processes with huge message queues or states
+ crash. </p> <p>The crash report generated by
+ <c>proc_lib</c> includes the new tag
+ <c>message_queue_len</c>. The neighbour report also
+ includes the new tag <c>current_stacktrace</c>. Finally,
+ the neighbour report no longer includes the tags
+ <c>messages</c> and <c>dictionary</c>. </p> <p> The new
+ function <c>error_logger:get_format_depth/0</c> can be
+ used to retrieve the value of the Kernel application
+ variable <c>error_logger_format_depth</c>. </p>
+ <p>
+ Own Id: OTP-14417</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/stdlib/doc/src/string.xml b/lib/stdlib/doc/src/string.xml
index 343904a49a..9d5edd9ecf 100644
--- a/lib/stdlib/doc/src/string.xml
+++ b/lib/stdlib/doc/src/string.xml
@@ -311,7 +311,9 @@ true</pre>
<desc>
<p>
Returns the first codepoint in <c><anno>String</anno></c>
- and the rest of <c><anno>String</anno></c> in the tail.
+ and the rest of <c><anno>String</anno></c> in the tail. Returns
+ an empty list if <c><anno>String</anno></c> is empty or an
+ <c>{error, String}</c> tuple if the next byte is invalid.
</p>
<p><em>Example:</em></p>
<pre>
@@ -326,7 +328,9 @@ true</pre>
<desc>
<p>
Returns the first grapheme cluster in <c><anno>String</anno></c>
- and the rest of <c><anno>String</anno></c> in the tail.
+ and the rest of <c><anno>String</anno></c> in the tail. Returns
+ an empty list if <c><anno>String</anno></c> is empty or an
+ <c>{error, String}</c> tuple if the next byte is invalid.
</p>
<p><em>Example:</em></p>
<pre>
diff --git a/lib/stdlib/doc/src/sys.xml b/lib/stdlib/doc/src/sys.xml
index f24e42bcee..64d8789016 100644
--- a/lib/stdlib/doc/src/sys.xml
+++ b/lib/stdlib/doc/src/sys.xml
@@ -308,7 +308,7 @@
format to the file. The events are formatted with a function that is
defined by the process that generated the event (with a call to
<seealso marker="#handle_debug/4"><c>handle_debug/4</c></seealso>).
- </p>
+ The file is opened with encoding UTF-8.</p>
</desc>
</func>
diff --git a/lib/stdlib/src/c.erl b/lib/stdlib/src/c.erl
index bb7b485490..c04a201ce1 100644
--- a/lib/stdlib/src/c.erl
+++ b/lib/stdlib/src/c.erl
@@ -144,7 +144,7 @@ c(SrcFile, NewOpts, Filter, BeamFile, Info) ->
F = fun (Opt) -> not is_outdir_opt(Opt) andalso Filter(Opt) end,
Options = (NewOpts ++ [{outdir,filename:dirname(BeamFile)}]
++ lists:filter(F, old_options(Info))),
- format("Recompiling ~s\n", [SrcFile]),
+ format("Recompiling ~ts\n", [SrcFile]),
safe_recompile(SrcFile, Options, BeamFile).
old_options(Info) ->
@@ -255,7 +255,7 @@ safe_recompile(File, Options, BeamFile) ->
compile_and_load(File, Opts0) when is_list(Opts0) ->
Opts = [report_errors, report_warnings
| ensure_from(filename:extension(File),
- ensure_outdir(filename:dirname(File), Opts0))],
+ ensure_outdir(".", Opts0))],
case compile:file(File, Opts) of
{ok,Mod} -> %Listing file.
purge_and_load(Mod, File, Opts);
@@ -548,7 +548,7 @@ mfa_string(Fun) when is_function(Fun) ->
{arity,A} = erlang:fun_info(Fun, arity),
mfa_string({M,F,A});
mfa_string({M,F,A}) ->
- io_lib:format("~w:~w/~w", [M,F,A]);
+ io_lib:format("~w:~tw/~w", [M,F,A]);
mfa_string(X) ->
w(X).
@@ -572,7 +572,7 @@ display_info(Pid) ->
w(Reds), w(LM)),
iformat(case fetch(registered_name, Info) of
0 -> "";
- X -> w(X)
+ X -> io_lib:format("~tw", [X])
end,
mfa_string(Curr),
w(SS),
@@ -594,7 +594,7 @@ initial_call(Info) ->
end.
iformat(A1, A2, A3, A4, A5) ->
- format("~-21s ~-33s ~8s ~8s ~4s~n", [A1,A2,A3,A4,A5]).
+ format("~-21ts ~-33ts ~8s ~8s ~4s~n", [A1,A2,A3,A4,A5]).
all_procs() ->
case is_alive() of
@@ -767,7 +767,7 @@ print_exports(X) when length(X) > 16 ->
split_print_exports(X);
print_exports([]) -> ok;
print_exports([{F, A} |Tail]) ->
- format(" ~w/~w~n",[F, A]),
+ format(" ~tw/~w~n",[F, A]),
print_exports(Tail).
split_print_exports(L) ->
@@ -779,11 +779,11 @@ split_print_exports(L) ->
split_print_exports([], [{F, A}|T]) ->
Str = " ",
- format("~-30s~w/~w~n", [Str, F, A]),
+ format("~-30ts~tw/~w~n", [Str, F, A]),
split_print_exports([], T);
split_print_exports([{F1, A1}|T1], [{F2, A2} | T2]) ->
- Str = flatten(io_lib:format("~w/~w", [F1, A1])),
- format("~-30s~w/~w~n", [Str, F2, A2]),
+ Str = flatten(io_lib:format("~tw/~w", [F1, A1])),
+ format("~-30ts~tw/~w~n", [Str, F2, A2]),
split_print_exports(T1, T2);
split_print_exports([], []) -> ok.
@@ -883,22 +883,22 @@ procline(Name, Info, Pid) ->
Call = initial_call(Info),
Reds = fetch(reductions, Info),
LM = length(fetch(messages, Info)),
- procformat(io_lib:format("~w",[Name]),
+ procformat(io_lib:format("~tw",[Name]),
io_lib:format("~w",[Pid]),
- io_lib:format("~s",[mfa_string(Call)]),
+ io_lib:format("~ts",[mfa_string(Call)]),
integer_to_list(Reds), integer_to_list(LM)).
procformat(Name, Pid, Call, Reds, LM) ->
- format("~-21s ~-12s ~-25s ~12s ~4s~n", [Name,Pid,Call,Reds,LM]).
+ format("~-21ts ~-12s ~-25ts ~12s ~4s~n", [Name,Pid,Call,Reds,LM]).
portline(Name, Info, Id) ->
Cmd = fetch(name, Info),
- portformat(io_lib:format("~w",[Name]),
+ portformat(io_lib:format("~tw",[Name]),
erlang:port_to_list(Id),
Cmd).
portformat(Name, Id, Cmd) ->
- format("~-21s ~-15s ~-40s~n", [Name,Id,Cmd]).
+ format("~-21ts ~-15s ~-40ts~n", [Name,Id,Cmd]).
%% pwd()
%% cd(Directory)
diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl
index b35e9575a4..31d0d499e3 100644
--- a/lib/stdlib/src/epp.erl
+++ b/lib/stdlib/src/epp.erl
@@ -194,27 +194,27 @@ format_error(missing_parenthesis) ->
format_error(premature_end) ->
"premature end";
format_error({call,What}) ->
- io_lib:format("illegal macro call '~s'",[What]);
+ io_lib:format("illegal macro call '~ts'",[What]);
format_error({undefined,M,none}) ->
- io_lib:format("undefined macro '~s'", [M]);
+ io_lib:format("undefined macro '~ts'", [M]);
format_error({undefined,M,A}) ->
- io_lib:format("undefined macro '~s/~p'", [M,A]);
+ io_lib:format("undefined macro '~ts/~p'", [M,A]);
format_error({depth,What}) ->
io_lib:format("~s too deep",[What]);
format_error({mismatch,M}) ->
- io_lib:format("argument mismatch for macro '~s'", [M]);
+ io_lib:format("argument mismatch for macro '~ts'", [M]);
format_error({arg_error,M}) ->
- io_lib:format("badly formed argument for macro '~s'", [M]);
+ io_lib:format("badly formed argument for macro '~ts'", [M]);
format_error({redefine,M}) ->
- io_lib:format("redefining macro '~s'", [M]);
+ io_lib:format("redefining macro '~ts'", [M]);
format_error({redefine_predef,M}) ->
io_lib:format("redefining predefined macro '~s'", [M]);
format_error({circular,M,none}) ->
- io_lib:format("circular macro '~s'", [M]);
+ io_lib:format("circular macro '~ts'", [M]);
format_error({circular,M,A}) ->
- io_lib:format("circular macro '~s/~p'", [M,A]);
+ io_lib:format("circular macro '~ts/~p'", [M,A]);
format_error({include,W,F}) ->
- io_lib:format("can't find include ~s \"~s\"", [W,F]);
+ io_lib:format("can't find include ~s \"~ts\"", [W,F]);
format_error({illegal,How,What}) ->
io_lib:format("~s '-~s'", [How,What]);
format_error({illegal_function,Macro}) ->
@@ -224,9 +224,9 @@ format_error({illegal_function_usage,Macro}) ->
format_error({'NYI',What}) ->
io_lib:format("not yet implemented '~s'", [What]);
format_error({error,Term}) ->
- io_lib:format("-error(~p).", [Term]);
+ io_lib:format("-error(~tp).", [Term]);
format_error({warning,Term}) ->
- io_lib:format("-warning(~p).", [Term]);
+ io_lib:format("-warning(~tp).", [Term]);
format_error(E) -> file:format_error(E).
-spec parse_file(FileName, IncludePath, PredefMacros) ->
@@ -1307,7 +1307,7 @@ expand_macros([{'?',_Lq},Token|_Toks], _St) ->
Text;
undefined ->
Symbol = erl_scan:symbol(Token),
- io_lib:write(Symbol)
+ io_lib:fwrite(<<"~tp">>, [Symbol])
end,
throw({error,loc(Token),{call,[$?|T]}});
expand_macros([T|Ts], St) ->
diff --git a/lib/stdlib/src/erl_compile.erl b/lib/stdlib/src/erl_compile.erl
index 76db2eeacd..18d7548fdc 100644
--- a/lib/stdlib/src/erl_compile.erl
+++ b/lib/stdlib/src/erl_compile.erl
@@ -181,7 +181,7 @@ parse_generic_option("P", T, #options{specific=Spec}=Opts) ->
parse_generic_option("S", T, #options{specific=Spec}=Opts) ->
compile1(T, Opts#options{specific=['S'|Spec]});
parse_generic_option(Option, _T, _Opts) ->
- io:format(?STDERR, "Unknown option: -~s\n", [Option]),
+ io:format(?STDERR, "Unknown option: -~ts\n", [Option]),
usage().
parse_dep_option("", T) ->
@@ -202,7 +202,7 @@ parse_dep_option("T"++Opt, T0) ->
{Target,T} = get_option("MT", Opt, T0),
{[{makedep_target,Target}],T};
parse_dep_option(Opt, _T) ->
- io:format(?STDERR, "Unknown option: -M~s\n", [Opt]),
+ io:format(?STDERR, "Unknown option: -M~ts\n", [Opt]),
usage().
usage() ->
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index d53a31db0d..65ba343368 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -175,49 +175,48 @@ format_error(invalid_record) ->
"invalid record expression";
format_error({attribute,A}) ->
- io_lib:format("attribute '~w' after function definitions", [A]);
+ io_lib:format("attribute ~tw after function definitions", [A]);
format_error({missing_qlc_hrl,A}) ->
io_lib:format("qlc:q/~w called, but \"qlc.hrl\" not included", [A]);
format_error({redefine_import,{{F,A},M}}) ->
- io_lib:format("function ~w/~w already imported from ~w", [F,A,M]);
+ io_lib:format("function ~tw/~w already imported from ~w", [F,A,M]);
format_error({bad_inline,{F,A}}) ->
- io_lib:format("inlined function ~w/~w undefined", [F,A]);
+ io_lib:format("inlined function ~tw/~w undefined", [F,A]);
format_error({invalid_deprecated,D}) ->
- io_lib:format("badly formed deprecated attribute ~w", [D]);
+ io_lib:format("badly formed deprecated attribute ~tw", [D]);
format_error({bad_deprecated,{F,A}}) ->
- io_lib:format("deprecated function ~w/~w undefined or not exported", [F,A]);
+ io_lib:format("deprecated function ~tw/~w undefined or not exported",
+ [F,A]);
format_error({bad_nowarn_unused_function,{F,A}}) ->
- io_lib:format("function ~w/~w undefined", [F,A]);
+ io_lib:format("function ~tw/~w undefined", [F,A]);
format_error({bad_nowarn_bif_clash,{F,A}}) ->
- io_lib:format("function ~w/~w undefined", [F,A]);
+ io_lib:format("function ~tw/~w undefined", [F,A]);
format_error(disallowed_nowarn_bif_clash) ->
io_lib:format("compile directive nowarn_bif_clash is no longer allowed,~n"
" - use explicit module names or -compile({no_auto_import, [F/A]})", []);
-format_error({bad_nowarn_deprecated_function,{M,F,A}}) ->
- io_lib:format("~w:~w/~w is not a deprecated function", [M,F,A]);
format_error({bad_on_load,Term}) ->
- io_lib:format("badly formed on_load attribute: ~w", [Term]);
+ io_lib:format("badly formed on_load attribute: ~tw", [Term]);
format_error(multiple_on_loads) ->
"more than one on_load attribute";
format_error({bad_on_load_arity,{F,A}}) ->
- io_lib:format("function ~w/~w has wrong arity (must be 0)", [F,A]);
+ io_lib:format("function ~tw/~w has wrong arity (must be 0)", [F,A]);
format_error({undefined_on_load,{F,A}}) ->
- io_lib:format("function ~w/~w undefined", [F,A]);
+ io_lib:format("function ~tw/~w undefined", [F,A]);
format_error(export_all) ->
"export_all flag enabled - all functions will be exported";
format_error({duplicated_export, {F,A}}) ->
- io_lib:format("function ~w/~w already exported", [F,A]);
+ io_lib:format("function ~tw/~w already exported", [F,A]);
format_error({unused_import,{{F,A},M}}) ->
- io_lib:format("import ~w:~w/~w is unused", [M,F,A]);
+ io_lib:format("import ~w:~tw/~w is unused", [M,F,A]);
format_error({undefined_function,{F,A}}) ->
- io_lib:format("function ~w/~w undefined", [F,A]);
+ io_lib:format("function ~tw/~w undefined", [F,A]);
format_error({redefine_function,{F,A}}) ->
- io_lib:format("function ~w/~w already defined", [F,A]);
+ io_lib:format("function ~tw/~w already defined", [F,A]);
format_error({define_import,{F,A}}) ->
- io_lib:format("defining imported function ~w/~w", [F,A]);
+ io_lib:format("defining imported function ~tw/~w", [F,A]);
format_error({unused_function,{F,A}}) ->
- io_lib:format("function ~w/~w is unused", [F,A]);
+ io_lib:format("function ~tw/~w is unused", [F,A]);
format_error({call_to_redefined_bif,{F,A}}) ->
io_lib:format("ambiguous call of overridden auto-imported BIF ~w/~w~n"
" - use erlang:~w/~w or \"-compile({no_auto_import,[~w/~w]}).\" "
@@ -273,7 +272,7 @@ format_error(illegal_bin_pattern) ->
"binary patterns cannot be matched in parallel using '='";
format_error(illegal_expr) -> "illegal expression";
format_error({illegal_guard_local_call, {F,A}}) ->
- io_lib:format("call to local/imported function ~w/~w is illegal in guard",
+ io_lib:format("call to local/imported function ~tw/~w is illegal in guard",
[F,A]);
format_error(illegal_guard_expr) -> "illegal guard expression";
%% --- maps ---
@@ -281,23 +280,23 @@ format_error(illegal_map_construction) ->
"only association operators '=>' are allowed in map construction";
%% --- records ---
format_error({undefined_record,T}) ->
- io_lib:format("record ~w undefined", [T]);
+ io_lib:format("record ~tw undefined", [T]);
format_error({redefine_record,T}) ->
- io_lib:format("record ~w already defined", [T]);
+ io_lib:format("record ~tw already defined", [T]);
format_error({redefine_field,T,F}) ->
- io_lib:format("field ~w already defined in record ~w", [F,T]);
+ io_lib:format("field ~tw already defined in record ~tw", [F,T]);
format_error({undefined_field,T,F}) ->
- io_lib:format("field ~w undefined in record ~w", [F,T]);
+ io_lib:format("field ~tw undefined in record ~tw", [F,T]);
format_error(illegal_record_info) ->
"illegal record info";
format_error({field_name_is_variable,T,F}) ->
- io_lib:format("field ~w is not an atom or _ in record ~w", [F,T]);
+ io_lib:format("field ~tw is not an atom or _ in record ~tw", [F,T]);
format_error({wildcard_in_update,T}) ->
- io_lib:format("meaningless use of _ in update of record ~w", [T]);
+ io_lib:format("meaningless use of _ in update of record ~tw", [T]);
format_error({unused_record,T}) ->
- io_lib:format("record ~w is unused", [T]);
+ io_lib:format("record ~tw is unused", [T]);
format_error({untyped_record,T}) ->
- io_lib:format("record ~w has field(s) without type information", [T]);
+ io_lib:format("record ~tw has field(s) without type information", [T]);
%% --- variables ----
format_error({unbound_var,V}) ->
io_lib:format("variable ~w is unbound", [V]);
@@ -315,7 +314,7 @@ format_error({variable_in_record_def,V}) ->
io_lib:format("variable ~w in record definition", [V]);
%% --- binaries ---
format_error({undefined_bittype,Type}) ->
- io_lib:format("bit type ~w undefined", [Type]);
+ io_lib:format("bit type ~tw undefined", [Type]);
format_error({bittype_mismatch,Val1,Val2,What}) ->
io_lib:format("conflict in ~s specification for bit field: '~p' and '~p'",
[What,Val1,Val2]);
@@ -335,13 +334,13 @@ format_error(unsized_binary_in_bin_gen_pattern) ->
"binary fields without size are not allowed in patterns of bit string generators";
%% --- behaviours ---
format_error({conflicting_behaviours,{Name,Arity},B,FirstL,FirstB}) ->
- io_lib:format("conflicting behaviours - callback ~w/~w required by both '~p' "
+ io_lib:format("conflicting behaviours - callback ~tw/~w required by both '~p' "
"and '~p' ~s", [Name,Arity,B,FirstB,format_where(FirstL)]);
format_error({undefined_behaviour_func, {Func,Arity}, Behaviour}) ->
- io_lib:format("undefined callback function ~w/~w (behaviour '~w')",
+ io_lib:format("undefined callback function ~tw/~w (behaviour '~w')",
[Func,Arity,Behaviour]);
format_error({undefined_behaviour,Behaviour}) ->
- io_lib:format("behaviour ~w undefined", [Behaviour]);
+ io_lib:format("behaviour ~tw undefined", [Behaviour]);
format_error({undefined_behaviour_callbacks,Behaviour}) ->
io_lib:format("behaviour ~w callback functions are undefined",
[Behaviour]);
@@ -352,23 +351,23 @@ format_error({ill_defined_optional_callbacks,Behaviour}) ->
io_lib:format("behaviour ~w optional callback functions erroneously defined",
[Behaviour]);
format_error({behaviour_info, {_M,F,A}}) ->
- io_lib:format("cannot define callback attibute for ~w/~w when "
+ io_lib:format("cannot define callback attibute for ~tw/~w when "
"behaviour_info is defined",[F,A]);
format_error({redefine_optional_callback, {F, A}}) ->
- io_lib:format("optional callback ~w/~w duplicated", [F, A]);
+ io_lib:format("optional callback ~tw/~w duplicated", [F, A]);
format_error({undefined_callback, {_M, F, A}}) ->
- io_lib:format("callback ~w/~w is undefined", [F, A]);
+ io_lib:format("callback ~tw/~w is undefined", [F, A]);
%% --- types and specs ---
format_error({singleton_typevar, Name}) ->
io_lib:format("type variable ~w is only used once (is unbound)", [Name]);
format_error({bad_export_type, _ETs}) ->
io_lib:format("bad export_type declaration", []);
format_error({duplicated_export_type, {T, A}}) ->
- io_lib:format("type ~w/~w already exported", [T, A]);
+ io_lib:format("type ~tw/~w already exported", [T, A]);
format_error({undefined_type, {TypeName, Arity}}) ->
- io_lib:format("type ~w~s undefined", [TypeName, gen_type_paren(Arity)]);
+ io_lib:format("type ~tw~s undefined", [TypeName, gen_type_paren(Arity)]);
format_error({unused_type, {TypeName, Arity}}) ->
- io_lib:format("type ~w~s is unused", [TypeName, gen_type_paren(Arity)]);
+ io_lib:format("type ~tw~s is unused", [TypeName, gen_type_paren(Arity)]);
format_error({new_builtin_type, {TypeName, Arity}}) ->
io_lib:format("type ~w~s is a new builtin type; "
"its (re)definition is allowed only until the next release",
@@ -380,25 +379,26 @@ format_error({renamed_type, OldName, NewName}) ->
io_lib:format("type ~w() is now called ~w(); "
"please use the new name instead", [OldName, NewName]);
format_error({redefine_type, {TypeName, Arity}}) ->
- io_lib:format("type ~w~s already defined",
+ io_lib:format("type ~tw~s already defined",
[TypeName, gen_type_paren(Arity)]);
format_error({type_syntax, Constr}) ->
- io_lib:format("bad ~w type", [Constr]);
+ io_lib:format("bad ~tw type", [Constr]);
format_error(old_abstract_code) ->
io_lib:format("abstract code generated before Erlang/OTP 19.0 and "
"having typed record fields cannot be compiled", []);
format_error({redefine_spec, {M, F, A}}) ->
- io_lib:format("spec for ~w:~w/~w already defined", [M, F, A]);
+ io_lib:format("spec for ~tw:~tw/~w already defined", [M, F, A]);
format_error({redefine_spec, {F, A}}) ->
- io_lib:format("spec for ~w/~w already defined", [F, A]);
+ io_lib:format("spec for ~tw/~w already defined", [F, A]);
format_error({redefine_callback, {F, A}}) ->
- io_lib:format("callback ~w/~w already defined", [F, A]);
+ io_lib:format("callback ~tw/~w already defined", [F, A]);
format_error({bad_callback, {M, F, A}}) ->
- io_lib:format("explicit module not allowed for callback ~w:~w/~w ", [M, F, A]);
+ io_lib:format("explicit module not allowed for callback ~tw:~tw/~w",
+ [M, F, A]);
format_error({spec_fun_undefined, {F, A}}) ->
- io_lib:format("spec for undefined function ~w/~w", [F, A]);
+ io_lib:format("spec for undefined function ~tw/~w", [F, A]);
format_error({missing_spec, {F,A}}) ->
- io_lib:format("missing specification for function ~w/~w", [F, A]);
+ io_lib:format("missing specification for function ~tw/~w", [F, A]);
format_error(spec_wrong_arity) ->
"spec has wrong arity";
format_error(callback_wrong_arity) ->
@@ -417,15 +417,15 @@ format_error({deprecated_builtin_type, {Name, Arity},
"removed in ~s; use ~s",
[Name, Arity, Rel, UseS]);
format_error({not_exported_opaque, {TypeName, Arity}}) ->
- io_lib:format("opaque type ~w~s is not exported",
+ io_lib:format("opaque type ~tw~s is not exported",
[TypeName, gen_type_paren(Arity)]);
format_error({underspecified_opaque, {TypeName, Arity}}) ->
- io_lib:format("opaque type ~w~s is underspecified and therefore meaningless",
+ io_lib:format("opaque type ~tw~s is underspecified and therefore meaningless",
[TypeName, gen_type_paren(Arity)]);
format_error({bad_dialyzer_attribute,Term}) ->
- io_lib:format("badly formed dialyzer attribute: ~w", [Term]);
+ io_lib:format("badly formed dialyzer attribute: ~tw", [Term]);
format_error({bad_dialyzer_option,Term}) ->
- io_lib:format("unknown dialyzer warning option: ~w", [Term]);
+ io_lib:format("unknown dialyzer warning option: ~tw", [Term]);
%% --- obsolete? unused? ---
format_error({format_error, {Fmt, Args}}) ->
io_lib:format(Fmt, Args).
@@ -763,12 +763,7 @@ start_state({attribute,Line,module,{_,_}}=Form, St0) ->
start_state({attribute,Line,module,M}, St0) ->
St1 = St0#lint{module=M},
St2 = St1#lint{state=attribute},
- case is_non_latin1_name(M) of
- true ->
- add_error(Line, non_latin1_module_unsupported, St2);
- false ->
- St2
- end;
+ check_module_name(M, Line, St2);
start_state(Form, St) ->
Anno = case Form of
{eof, L} -> erl_anno:new(L);
@@ -778,9 +773,6 @@ start_state(Form, St) ->
St1 = add_error(Anno, undefined_module, St),
attribute_state(Form, St1#lint{state=attribute}).
-is_non_latin1_name(Name) ->
- lists:any(fun(C) -> C > 255 end, atom_to_list(Name)).
-
%% attribute_state(Form, State) ->
%% State'
@@ -862,9 +854,10 @@ not_deprecated(Forms, St0) ->
{nowarn_deprecated_function, MFAs0} <- lists:flatten([Args]),
MFA <- lists:flatten([MFAs0])],
Nowarn = [MFA || {MFA,_L} <- MFAsL],
- Bad = [MFAL || {{M,F,A},_L}=MFAL <- MFAsL,
- otp_internal:obsolete(M, F, A) =:= no],
- St1 = func_line_warning(bad_nowarn_deprecated_function, Bad, St0),
+ ML = [{M,L} || {{M,_F,_A},L} <- MFAsL, is_atom(M)],
+ St1 = foldl(fun ({M,L}, St2) ->
+ check_module_name(M, L, St2)
+ end, St0, ML),
St1#lint{not_deprecated = ordsets:from_list(Nowarn)}.
%% The nowarn_bif_clash directive is not only deprecated, it's actually an error from R14A
@@ -972,7 +965,8 @@ behaviour_callbacks(Line, B, St0) ->
catch
_:_ ->
St1 = add_warning(Line, {undefined_behaviour, B}, St0),
- {[], [], St1}
+ St2 = check_module_name(B, Line, St1),
+ {[], [], St2}
end.
behaviour_missing_callbacks([{{Line,B},Bfs0,OBfs}|T], St0) ->
@@ -1310,7 +1304,8 @@ exports(#lint{compile = Opts, defined = Defs, exports = Es}) ->
-type import() :: {module(), [fa()]} | module().
-spec import(line(), import(), lint_state()) -> lint_state().
-import(Line, {Mod,Fs}, St) ->
+import(Line, {Mod,Fs}, St00) ->
+ St = check_module_name(Mod, Line, St00),
Mfs = ordsets:from_list(Fs),
case check_imports(Line, Mfs, St#lint.imports) of
[] ->
@@ -2294,11 +2289,18 @@ expr({call,L,{tuple,Lt,[{atom,Lm,erlang},{atom,Lf,is_record}]},As}, Vt, St) ->
expr({call,Line,{remote,_Lr,{atom,_Lm,M},{atom,Lf,F}},As}, Vt, St0) ->
St1 = keyword_warning(Lf, F, St0),
St2 = check_remote_function(Line, M, F, As, St1),
- expr_list(As, Vt, St2);
+ St3 = check_module_name(M, Line, St2),
+ expr_list(As, Vt, St3);
expr({call,Line,{remote,_Lr,M,F},As}, Vt, St0) ->
St1 = keyword_warning(Line, M, St0),
St2 = keyword_warning(Line, F, St1),
- expr_list([M,F|As], Vt, St2);
+ St3 = case M of
+ {atom,Lm,Mod} ->
+ check_module_name(Mod, Lm, St2);
+ _ ->
+ St2
+ end,
+ expr_list([M,F|As], Vt, St3);
expr({call,Line,{atom,La,F},As}, Vt, St0) ->
St1 = keyword_warning(La, F, St0),
{Asvt,St2} = expr_list(As, Vt, St1),
@@ -2814,7 +2816,8 @@ check_type(Types, St) ->
check_type({ann_type, _L, [_Var, Type]}, SeenVars, St) ->
check_type(Type, SeenVars, St);
check_type({remote_type, L, [{atom, _, Mod}, {atom, _, Name}, Args]},
- SeenVars, St0) ->
+ SeenVars, St00) ->
+ St0 = check_module_name(Mod, L, St00),
St = deprecated_type(L, Mod, Name, Args, St0),
CurrentMod = St#lint.module,
case Mod =:= CurrentMod of
@@ -2973,11 +2976,12 @@ obsolete_builtin_type({Name, A}) when is_atom(Name), is_integer(A) -> no.
%% spec_decl(Line, Fun, Types, State) -> State.
-spec_decl(Line, MFA0, TypeSpecs, St0 = #lint{specs = Specs, module = Mod}) ->
+spec_decl(Line, MFA0, TypeSpecs, St00 = #lint{specs = Specs, module = Mod}) ->
MFA = case MFA0 of
{F, Arity} -> {Mod, F, Arity};
{_M, _F, Arity} -> MFA0
end,
+ St0 = check_module_name(element(1, MFA), Line, St00),
St1 = St0#lint{specs = dict:store(MFA, Line, Specs)},
case dict:is_key(MFA, Specs) of
true -> add_error(Line, {redefine_spec, MFA0}, St1);
@@ -2989,7 +2993,9 @@ spec_decl(Line, MFA0, TypeSpecs, St0 = #lint{specs = Specs, module = Mod}) ->
callback_decl(Line, MFA0, TypeSpecs,
St0 = #lint{callbacks = Callbacks, module = Mod}) ->
case MFA0 of
- {_M, _F, _A} -> add_error(Line, {bad_callback, MFA0}, St0);
+ {M, _F, _A} ->
+ St1 = check_module_name(M, Line, St0),
+ add_error(Line, {bad_callback, MFA0}, St1);
{F, Arity} ->
MFA = {Mod, F, Arity},
St1 = St0#lint{callbacks = dict:store(MFA, Line, Callbacks)},
@@ -3033,6 +3039,16 @@ is_fa({FuncName, Arity})
when is_atom(FuncName), is_integer(Arity), Arity >= 0 -> true;
is_fa(_) -> false.
+check_module_name(M, Line, St) ->
+ case is_latin1_name(M) of
+ true -> St;
+ false ->
+ add_error(Line, non_latin1_module_unsupported, St)
+ end.
+
+is_latin1_name(Name) ->
+ io_lib:latin1_char_list(atom_to_list(Name)).
+
check_specs([FunType|Left], ETag, Arity, St0) ->
{FunType1, CTypes} =
case FunType of
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index 2dcddeb8c2..6e72d64acc 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -1052,6 +1052,9 @@ build_typed_attribute({atom,Aa,record},
build_typed_attribute({atom,Aa,Attr},
{type_def, {call,_,{atom,_,TypeName},Args}, Type})
when Attr =:= 'type' ; Attr =:= 'opaque' ->
+ lists:foreach(fun({var, A, '_'}) -> ret_err(A, "bad type variable");
+ (_) -> ok
+ end, Args),
case lists:all(fun({var, _, _}) -> true;
(_) -> false
end, Args) of
@@ -1094,12 +1097,12 @@ build_compat_constraint({atom, _, is_subtype}, [{var, _, _}=LHS, Type]) ->
build_compat_constraint({atom, _, is_subtype}, [LHS, _Type]) ->
ret_err(?anno(LHS), "bad type variable");
build_compat_constraint({atom, A, Atom}, _Types) ->
- ret_err(A, io_lib:format("unsupported constraint ~w", [Atom])).
+ ret_err(A, io_lib:format("unsupported constraint ~tw", [Atom])).
build_constraint({atom, _, is_subtype}, [{var, _, _}=LHS, Type]) ->
build_constraint(LHS, Type);
build_constraint({atom, A, Atom}, _Foo) ->
- ret_err(A, io_lib:format("unsupported constraint ~w", [Atom]));
+ ret_err(A, io_lib:format("unsupported constraint ~tw", [Atom]));
build_constraint({var, A, '_'}, _Types) ->
ret_err(A, "bad type variable");
build_constraint(LHS, Type) ->
@@ -1217,7 +1220,7 @@ attribute_farity_map(Args) ->
-spec error_bad_decl(erl_anno:anno(), attributes()) -> no_return().
error_bad_decl(Anno, S) ->
- ret_err(Anno, io_lib:format("bad ~w declaration", [S])).
+ ret_err(Anno, io_lib:format("bad ~tw declaration", [S])).
farity_list({cons,_Ac,{op,_Ao,'/',{atom,_Aa,A},{integer,_Ai,I}},Tail}) ->
[{A,I}|farity_list(Tail)];
diff --git a/lib/stdlib/src/erl_tar.erl b/lib/stdlib/src/erl_tar.erl
index 168ea4002c..76f0b38108 100644
--- a/lib/stdlib/src/erl_tar.erl
+++ b/lib/stdlib/src/erl_tar.erl
@@ -176,7 +176,7 @@ check_extract(Name, #read_opts{files=Files}) ->
-type tar_entry() :: {filename(),
typeflag(),
non_neg_integer(),
- calendar:datetime(),
+ tar_time(),
mode(),
uid(),
gid()}.
@@ -274,8 +274,13 @@ mode_to_string(Mode, [_|T], Acc) ->
mode_to_string(_, [], Acc) ->
Acc.
-%% Converts a datetime tuple to a readable string
-time_to_string({{Y, Mon, Day}, {H, Min, _}}) ->
+%% Converts a tar_time() (POSIX time) to a readable string
+time_to_string(Secs0) ->
+ Epoch = calendar:datetime_to_gregorian_seconds(?EPOCH),
+ Secs = Epoch + Secs0,
+ DateTime0 = calendar:gregorian_seconds_to_datetime(Secs),
+ DateTime = calendar:universal_time_to_local_time(DateTime0),
+ {{Y, Mon, Day}, {H, Min, _}} = DateTime,
io_lib:format("~s ~2w ~s:~s ~w", [month(Mon), Day, two_d(H), two_d(Min), Y]).
two_d(N) ->
@@ -452,7 +457,8 @@ add(Reader, NameOrBin, NameInArchive, Options)
do_add(#reader{access=write}=Reader, Name, NameInArchive, Options)
when is_list(NameInArchive), is_list(Options) ->
- Opts = #add_opts{read_info=fun(F) -> file:read_link_info(F) end},
+ RF = fun(F) -> file:read_link_info(F, [{time, posix}]) end,
+ Opts = #add_opts{read_info=RF},
add1(Reader, Name, NameInArchive, add_opts(Options, Opts));
do_add(#reader{access=read},_,_,_) ->
{error, eacces};
@@ -460,7 +466,8 @@ do_add(Reader,_,_,_) ->
{error, {badarg, Reader}}.
add_opts([dereference|T], Opts) ->
- add_opts(T, Opts#add_opts{read_info=fun(F) -> file:read_file_info(F) end});
+ RF = fun(F) -> file:read_file_info(F, [{time, posix}]) end,
+ add_opts(T, Opts#add_opts{read_info=RF});
add_opts([verbose|T], Opts) ->
add_opts(T, Opts#add_opts{verbose=true});
add_opts([{chunks,N}|T], Opts) ->
@@ -503,7 +510,7 @@ add1(#reader{}=Reader, Name, NameInArchive, #add_opts{read_info=ReadInfo}=Opts)
end;
add1(Reader, Bin, NameInArchive, Opts) when is_binary(Bin) ->
add_verbose(Opts, "a ~ts~n", [NameInArchive]),
- Now = calendar:now_to_local_time(erlang:timestamp()),
+ Now = os:system_time(seconds),
Header = #tar_header{
name = NameInArchive,
size = byte_size(Bin),
@@ -612,7 +619,7 @@ build_header(#tar_header{}=Header, Opts) ->
devmajor=Devmaj,
devminor=Devmin
} = Header,
- Mtime = datetime_to_posix(Header#tar_header.mtime),
+ Mtime = Header#tar_header.mtime,
Block0 = ?ZERO_BLOCK,
{Block1, Pax0} = write_string(Block0, ?V7_NAME, ?V7_NAME_LEN, Name, ?PAX_PATH, #{}),
@@ -770,14 +777,6 @@ join_split_ustar_path([Part|Rest], {ok, Name, nil}) ->
join_split_ustar_path([Part|Rest], {ok, Name, Acc}) ->
join_split_ustar_path(Rest, {ok, Name, <<Acc/binary,$/,Part/binary>>}).
-datetime_to_posix(DateTime) ->
- Epoch = calendar:datetime_to_gregorian_seconds(?EPOCH),
- Secs = calendar:datetime_to_gregorian_seconds(DateTime),
- case Secs - Epoch of
- N when N < 0 -> 0;
- N -> N
- end.
-
write_octal(Block, Pos, Size, X) ->
Octal = zero_pad(format_octal(X), Size-1),
if byte_size(Octal) < Size ->
@@ -984,7 +983,7 @@ do_get_format(#header_v7{}=V7, Bin)
unpack_format(Format, #header_v7{}=V7, Bin, Reader)
when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE ->
- Mtime = posix_to_erlang_time(parse_numeric(V7#header_v7.mtime)),
+ Mtime = parse_numeric(V7#header_v7.mtime),
Header0 = #tar_header{
name=parse_string(V7#header_v7.name),
mode=parse_numeric(V7#header_v7.mode),
@@ -1051,9 +1050,9 @@ unpack_modern(Format, #header_v7{}=V7, Bin, #tar_header{}=Header0)
Star = to_star(V7, Bin),
Prefix0 = parse_string(Star#header_star.prefix),
Atime0 = Star#header_star.atime,
- Atime = posix_to_erlang_time(parse_numeric(Atime0)),
+ Atime = parse_numeric(Atime0),
Ctime0 = Star#header_star.ctime,
- Ctime = posix_to_erlang_time(parse_numeric(Ctime0)),
+ Ctime = parse_numeric(Ctime0),
{Prefix0, H1#tar_header{
atime=Atime,
ctime=Ctime
@@ -1313,11 +1312,6 @@ is_header_only_type(?TYPE_LINK) -> true;
is_header_only_type(?TYPE_DIR) -> true;
is_header_only_type(_) -> false.
-posix_to_erlang_time(Sec) ->
- OneMillion = 1000000,
- Time = calendar:now_to_datetime({Sec div OneMillion, Sec rem OneMillion, 0}),
- erlang:universaltime_to_localtime(Time).
-
foldl_read(#reader{access=read}=Reader, Fun, Accu, #read_opts{}=Opts)
when is_function(Fun,4) ->
case foldl_read0(Reader, Fun, Accu, Opts) of
@@ -1423,7 +1417,7 @@ do_merge_pax(Header, [_Ignore|Rest]) ->
do_merge_pax(Header, Rest).
%% Returns the time since UNIX epoch as a datetime
--spec parse_pax_time(binary()) -> calendar:datetime().
+-spec parse_pax_time(binary()) -> tar_time().
parse_pax_time(Bin) when is_binary(Bin) ->
TotalNano = case binary:split(Bin, [<<$.>>]) of
[SecondsStr, NanoStr0] ->
@@ -1450,8 +1444,7 @@ parse_pax_time(Bin) when is_binary(Bin) ->
Micro = TotalNano div 1000,
Mega = Micro div 1000000000000,
Secs = Micro div 1000000 - (Mega*1000000),
- Micro2 = Micro rem 1000000,
- calendar:now_to_datetime({Mega, Secs, Micro2}).
+ Secs.
%% Given a regular file reader, reads the whole file and
%% parses all extended attributes it contains.
@@ -1671,7 +1664,7 @@ set_extracted_file_info(Name, #tar_header{typeflag = ?TYPE_BLOCK}=Header) ->
set_device_info(Name, Header);
set_extracted_file_info(Name, #tar_header{mtime=Mtime,mode=Mode}) ->
Info = #file_info{mode=Mode, mtime=Mtime},
- file:write_file_info(Name, Info).
+ file:write_file_info(Name, Info, [{time, posix}]).
set_device_info(Name, #tar_header{}=Header) ->
Mtime = Header#tar_header.mtime,
diff --git a/lib/stdlib/src/erl_tar.hrl b/lib/stdlib/src/erl_tar.hrl
index d646d02989..cff0c2f500 100644
--- a/lib/stdlib/src/erl_tar.hrl
+++ b/lib/stdlib/src/erl_tar.hrl
@@ -55,6 +55,8 @@
{string(), binary()} |
{string(), file:filename()}].
+-type tar_time() :: non_neg_integer().
+
%% The tar header, once fully parsed.
-record(tar_header, {
name = "" :: string(), %% name of header file entry
@@ -62,15 +64,15 @@
uid = 0 :: non_neg_integer(), %% user id of owner
gid = 0 :: non_neg_integer(), %% group id of owner
size = 0 :: non_neg_integer(), %% length in bytes
- mtime :: calendar:datetime(), %% modified time
+ mtime :: tar_time(), %% modified time
typeflag :: char(), %% type of header entry
linkname = "" :: string(), %% target name of link
uname = "" :: string(), %% user name of owner
gname = "" :: string(), %% group name of owner
devmajor = 0 :: non_neg_integer(), %% major number of character or block device
devminor = 0 :: non_neg_integer(), %% minor number of character or block device
- atime :: calendar:datetime(), %% access time
- ctime :: calendar:datetime() %% status change time
+ atime :: tar_time(), %% access time
+ ctime :: tar_time() %% status change time
}).
-type tar_header() :: #tar_header{}.
diff --git a/lib/stdlib/src/error_logger_file_h.erl b/lib/stdlib/src/error_logger_file_h.erl
index 0b262de3ab..b7c193f965 100644
--- a/lib/stdlib/src/error_logger_file_h.erl
+++ b/lib/stdlib/src/error_logger_file_h.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -55,9 +55,9 @@ init(File) ->
init(File, PrevHandler) ->
process_flag(trap_exit, true),
- case file:open(File, [write]) of
+ case file:open(File, [write,{encoding,utf8}]) of
{ok,Fd} ->
- Depth = get_depth(),
+ Depth = error_logger:get_format_depth(),
State = #st{fd=Fd,filename=File,prev_handler=PrevHandler,
depth=Depth},
{ok, State};
@@ -65,14 +65,6 @@ init(File, PrevHandler) ->
Error
end.
-get_depth() ->
- case application:get_env(kernel, error_logger_format_depth) of
- {ok, Depth} when is_integer(Depth) ->
- max(10, Depth);
- undefined ->
- unlimited
- end.
-
handle_event({_Type, GL, _Msg}, State) when node(GL) =/= node() ->
{ok, State};
handle_event(Event, State) ->
diff --git a/lib/stdlib/src/error_logger_tty_h.erl b/lib/stdlib/src/error_logger_tty_h.erl
index 2f2fd65252..8f0d7b0362 100644
--- a/lib/stdlib/src/error_logger_tty_h.erl
+++ b/lib/stdlib/src/error_logger_tty_h.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -44,7 +44,7 @@
%% This one is used when we takeover from the simple error_logger.
init({[], {error_logger, Buf}}) ->
User = set_group_leader(),
- Depth = get_depth(),
+ Depth = error_logger:get_format_depth(),
State = #st{user=User,prev_handler=error_logger,depth=Depth},
write_events(State, Buf),
{ok, State};
@@ -56,17 +56,9 @@ init({[], {error_logger_tty_h, PrevHandler}}) ->
%% This one is used when we are started directly.
init([]) ->
User = set_group_leader(),
- Depth = get_depth(),
+ Depth = error_logger:get_format_depth(),
{ok, #st{user=User,prev_handler=[],depth=Depth}}.
-get_depth() ->
- case application:get_env(kernel, error_logger_format_depth) of
- {ok, Depth} when is_integer(Depth) ->
- max(10, Depth);
- undefined ->
- unlimited
- end.
-
handle_event({_Type, GL, _Msg}, State) when node(GL) =/= node() ->
{ok, State};
handle_event(Event, State) ->
diff --git a/lib/stdlib/src/escript.erl b/lib/stdlib/src/escript.erl
index f2629a47c2..2093916a7c 100644
--- a/lib/stdlib/src/escript.erl
+++ b/lib/stdlib/src/escript.erl
@@ -281,12 +281,12 @@ start(EscriptOptions) ->
end
catch
throw:Str ->
- io:format("escript: ~s\n", [Str]),
+ io:format("escript: ~ts\n", [Str]),
my_halt(127);
_:Reason ->
Stk = erlang:get_stacktrace(),
- io:format("escript: Internal error: ~p\n", [Reason]),
- io:format("~p\n", [Stk]),
+ io:format("escript: Internal error: ~tp\n", [Reason]),
+ io:format("~tp\n", [Stk]),
my_halt(127)
end.
diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl
index 257c829801..32f43fc706 100644
--- a/lib/stdlib/src/gen.erl
+++ b/lib/stdlib/src/gen.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl
index ba0a7ae8e5..a3d53efd0d 100644
--- a/lib/stdlib/src/gen_server.erl
+++ b/lib/stdlib/src/gen_server.erl
@@ -107,7 +107,9 @@
%% Internal exports
-export([init_it/6]).
--import(error_logger, [format/2]).
+-define(
+ STACKTRACE(),
+ try throw(ok) catch _ -> erlang:get_stacktrace() end).
%%%=========================================================================
%%% API
@@ -326,15 +328,16 @@ init_it(Starter, self, Name, Mod, Args, Options) ->
init_it(Starter, Parent, Name0, Mod, Args, Options) ->
Name = gen:name(Name0),
Debug = gen:debug_options(Name, Options),
- HibernateAfterTimeout = gen:hibernate_after(Options),
- case catch Mod:init(Args) of
- {ok, State} ->
+ HibernateAfterTimeout = gen:hibernate_after(Options),
+
+ case init_it(Mod, Args) of
+ {ok, {ok, State}} ->
proc_lib:init_ack(Starter, {ok, self()}),
loop(Parent, Name, State, Mod, infinity, HibernateAfterTimeout, Debug);
- {ok, State, Timeout} ->
+ {ok, {ok, State, Timeout}} ->
proc_lib:init_ack(Starter, {ok, self()}),
loop(Parent, Name, State, Mod, Timeout, HibernateAfterTimeout, Debug);
- {stop, Reason} ->
+ {ok, {stop, Reason}} ->
%% For consistency, we must make sure that the
%% registered name (if any) is unregistered before
%% the parent process is notified about the failure.
@@ -344,18 +347,25 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) ->
gen:unregister_name(Name0),
proc_lib:init_ack(Starter, {error, Reason}),
exit(Reason);
- ignore ->
+ {ok, ignore} ->
gen:unregister_name(Name0),
proc_lib:init_ack(Starter, ignore),
exit(normal);
- {'EXIT', Reason} ->
- gen:unregister_name(Name0),
- proc_lib:init_ack(Starter, {error, Reason}),
- exit(Reason);
- Else ->
+ {ok, Else} ->
Error = {bad_return_value, Else},
proc_lib:init_ack(Starter, {error, Error}),
- exit(Error)
+ exit(Error);
+ {'EXIT', Class, Reason, Stacktrace} ->
+ gen:unregister_name(Name0),
+ proc_lib:init_ack(Starter, {error, terminate_reason(Class, Reason, Stacktrace)}),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+init_it(Mod, Args) ->
+ try
+ {ok, Mod:init(Args)}
+ catch
+ throw:R -> {ok, R};
+ Class:R -> {'EXIT', Class, R, erlang:get_stacktrace()}
end.
%%%========================================================================
@@ -397,7 +407,7 @@ decode_msg(Msg, Parent, Name, State, Mod, Time, HibernateAfterTimeout, Debug, Hi
sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
[Name, State, Mod, Time, HibernateAfterTimeout], Hib);
{'EXIT', Parent, Reason} ->
- terminate(Reason, Name, undefined, Msg, Mod, State, Debug);
+ terminate(Reason, ?STACKTRACE(), Name, undefined, Msg, Mod, State, Debug);
_Msg when Debug =:= [] ->
handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout);
_Msg ->
@@ -589,17 +599,11 @@ start_monitor(Node, Name) when is_atom(Node), is_atom(Name) ->
%% ---------------------------------------------------
%% Helper functions for try-catch of callbacks.
%% Returns the return value of the callback, or
-%% {'EXIT', ExitReason, ReportReason} (if an exception occurs)
-%%
-%% ExitReason is the reason that shall be used when the process
-%% terminates.
+%% {'EXIT', Class, Reason, Stack} (if an exception occurs)
%%
-%% ReportReason is the reason that shall be printed in the error
-%% report.
-%%
-%% These functions are introduced in order to add the stack trace in
-%% the error report produced when a callback is terminated with
-%% erlang:exit/1 (OTP-12263).
+%% The Class, Reason and Stack are given to erlang:raise/3
+%% to make sure proc_lib receives the proper reasons and
+%% stacktraces.
%% ---------------------------------------------------
try_dispatch({'$gen_cast', Msg}, Mod, State) ->
@@ -621,15 +625,10 @@ try_dispatch(Mod, Func, Msg, State) ->
[Mod, Msg]),
{ok, {noreply, State}};
true ->
- Stacktrace = erlang:get_stacktrace(),
- {'EXIT', {R, Stacktrace}, {R, Stacktrace}}
+ {'EXIT', error, R, erlang:get_stacktrace()}
end;
- error:R ->
- Stacktrace = erlang:get_stacktrace(),
- {'EXIT', {R, Stacktrace}, {R, Stacktrace}};
- exit:R ->
- Stacktrace = erlang:get_stacktrace(),
- {'EXIT', R, {R, Stacktrace}}
+ Class:R ->
+ {'EXIT', Class, R, erlang:get_stacktrace()}
end.
try_handle_call(Mod, Msg, From, State) ->
@@ -638,12 +637,8 @@ try_handle_call(Mod, Msg, From, State) ->
catch
throw:R ->
{ok, R};
- error:R ->
- Stacktrace = erlang:get_stacktrace(),
- {'EXIT', {R, Stacktrace}, {R, Stacktrace}};
- exit:R ->
- Stacktrace = erlang:get_stacktrace(),
- {'EXIT', R, {R, Stacktrace}}
+ Class:R ->
+ {'EXIT', Class, R, erlang:get_stacktrace()}
end.
try_terminate(Mod, Reason, State) ->
@@ -654,12 +649,8 @@ try_terminate(Mod, Reason, State) ->
catch
throw:R ->
{ok, R};
- error:R ->
- Stacktrace = erlang:get_stacktrace(),
- {'EXIT', {R, Stacktrace}, {R, Stacktrace}};
- exit:R ->
- Stacktrace = erlang:get_stacktrace(),
- {'EXIT', R, {R, Stacktrace}}
+ Class:R ->
+ {'EXIT', Class, R, erlang:get_stacktrace()}
end;
false ->
{ok, ok}
@@ -684,10 +675,11 @@ handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, HibernateAfterTim
{ok, {noreply, NState, Time1}} ->
loop(Parent, Name, NState, Mod, Time1, HibernateAfterTimeout, []);
{ok, {stop, Reason, Reply, NState}} ->
- {'EXIT', R} =
- (catch terminate(Reason, Name, From, Msg, Mod, NState, [])),
- reply(From, Reply),
- exit(R);
+ try
+ terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, [])
+ after
+ reply(From, Reply)
+ end;
Other -> handle_common_reply(Other, Parent, Name, From, Msg, Mod, HibernateAfterTimeout, State)
end;
handle_msg(Msg, Parent, Name, State, Mod, HibernateAfterTimeout) ->
@@ -712,10 +704,11 @@ handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, HibernateAfterTim
{noreply, NState}),
loop(Parent, Name, NState, Mod, Time1, HibernateAfterTimeout, Debug1);
{ok, {stop, Reason, Reply, NState}} ->
- {'EXIT', R} =
- (catch terminate(Reason, Name, From, Msg, Mod, NState, Debug)),
- _ = reply(Name, From, Reply, NState, Debug),
- exit(R);
+ try
+ terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, Debug)
+ after
+ _ = reply(Name, From, Reply, NState, Debug)
+ end;
Other ->
handle_common_reply(Other, Parent, Name, From, Msg, Mod, HibernateAfterTimeout, State, Debug)
end;
@@ -730,11 +723,11 @@ handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout,
{ok, {noreply, NState, Time1}} ->
loop(Parent, Name, NState, Mod, Time1, HibernateAfterTimeout, []);
{ok, {stop, Reason, NState}} ->
- terminate(Reason, Name, From, Msg, Mod, NState, []);
- {'EXIT', ExitReason, ReportReason} ->
- terminate(ExitReason, ReportReason, Name, From, Msg, Mod, State, []);
+ terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, []);
+ {'EXIT', Class, Reason, Stacktrace} ->
+ terminate(Class, Reason, Stacktrace, Name, From, Msg, Mod, State, []);
{ok, BadReply} ->
- terminate({bad_return_value, BadReply}, Name, From, Msg, Mod, State, [])
+ terminate({bad_return_value, BadReply}, ?STACKTRACE(), Name, From, Msg, Mod, State, [])
end.
handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout, State, Debug) ->
@@ -748,11 +741,11 @@ handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout,
{noreply, NState}),
loop(Parent, Name, NState, Mod, Time1, HibernateAfterTimeout, Debug1);
{ok, {stop, Reason, NState}} ->
- terminate(Reason, Name, From, Msg, Mod, NState, Debug);
- {'EXIT', ExitReason, ReportReason} ->
- terminate(ExitReason, ReportReason, Name, From, Msg, Mod, State, Debug);
+ terminate(Reason, ?STACKTRACE(), Name, From, Msg, Mod, NState, Debug);
+ {'EXIT', Class, Reason, Stacktrace} ->
+ terminate(Class, Reason, Stacktrace, Name, From, Msg, Mod, State, Debug);
{ok, BadReply} ->
- terminate({bad_return_value, BadReply}, Name, From, Msg, Mod, State, Debug)
+ terminate({bad_return_value, BadReply}, ?STACKTRACE(), Name, From, Msg, Mod, State, Debug)
end.
reply(Name, {To, Tag}, Reply, State, Debug) ->
@@ -770,7 +763,7 @@ system_continue(Parent, Debug, [Name, State, Mod, Time, HibernateAfterTimeout])
-spec system_terminate(_, _, _, [_]) -> no_return().
system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time, _HibernateAfterTimeout]) ->
- terminate(Reason, Name, undefined, [], Mod, State, Debug).
+ terminate(Reason, ?STACKTRACE(), Name, undefined, [], Mod, State, Debug).
system_code_change([Name, State, Mod, Time, HibernateAfterTimeout], _Module, OldVsn, Extra) ->
case catch Mod:code_change(OldVsn, State, Extra) of
@@ -811,35 +804,58 @@ print_event(Dev, Event, Name) ->
%%% ---------------------------------------------------
%%% Terminate the server.
+%%%
+%%% terminate/8 is triggered by {stop, Reason} or bad
+%%% return values. The stacktrace is generated via the
+%%% ?STACKTRACE() macro and the ReportReason must not
+%%% be wrapped in tuples.
+%%%
+%%% terminate/9 is triggered in case of error/exit in
+%%% the user callback. In this case the report reason
+%%% always includes the user stacktrace.
+%%%
+%%% The reason received in the terminate/2 callbacks
+%%% always includes the stacktrace for errors and never
+%%% for exits.
%%% ---------------------------------------------------
-
--spec terminate(_, _, _, _, _, _, _) -> no_return().
-terminate(Reason, Name, From, Msg, Mod, State, Debug) ->
- terminate(Reason, Reason, Name, From, Msg, Mod, State, Debug).
-spec terminate(_, _, _, _, _, _, _, _) -> no_return().
-terminate(ExitReason, ReportReason, Name, From, Msg, Mod, State, Debug) ->
- Reply = try_terminate(Mod, ExitReason, State),
+terminate(Reason, Stacktrace, Name, From, Msg, Mod, State, Debug) ->
+ terminate(exit, Reason, Stacktrace, Reason, Name, From, Msg, Mod, State, Debug).
+
+-spec terminate(_, _, _, _, _, _, _, _, _) -> no_return().
+terminate(Class, Reason, Stacktrace, Name, From, Msg, Mod, State, Debug) ->
+ ReportReason = {Reason, Stacktrace},
+ terminate(Class, Reason, Stacktrace, ReportReason, Name, From, Msg, Mod, State, Debug).
+
+-spec terminate(_, _, _, _, _, _, _, _, _, _) -> no_return().
+terminate(Class, Reason, Stacktrace, ReportReason, Name, From, Msg, Mod, State, Debug) ->
+ Reply = try_terminate(Mod, terminate_reason(Class, Reason, Stacktrace), State),
case Reply of
- {'EXIT', ExitReason1, ReportReason1} ->
+ {'EXIT', C, R, S} ->
FmtState = format_status(terminate, Mod, get(), State),
- error_info(ReportReason1, Name, From, Msg, FmtState, Debug),
- exit(ExitReason1);
+ error_info({R, S}, Name, From, Msg, FmtState, Debug),
+ erlang:raise(C, R, S);
_ ->
- case ExitReason of
- normal ->
- exit(normal);
- shutdown ->
- exit(shutdown);
- {shutdown,_}=Shutdown ->
- exit(Shutdown);
+ case {Class, Reason} of
+ {exit, normal} -> ok;
+ {exit, shutdown} -> ok;
+ {exit, {shutdown,_}} -> ok;
_ ->
FmtState = format_status(terminate, Mod, get(), State),
- error_info(ReportReason, Name, From, Msg, FmtState, Debug),
- exit(ExitReason)
+ error_info(ReportReason, Name, From, Msg, FmtState, Debug)
end
+ end,
+ case Stacktrace of
+ [] ->
+ erlang:Class(Reason);
+ _ ->
+ erlang:raise(Class, Reason, Stacktrace)
end.
+terminate_reason(error, Reason, Stacktrace) -> {Reason, Stacktrace};
+terminate_reason(exit, Reason, _Stacktrace) -> Reason.
+
error_info(_Reason, application_controller, _From, _Msg, _State, _Debug) ->
%% OTP-5811 Don't send an error report if it's the system process
%% application_controller which is terminating - let init take care
@@ -861,14 +877,15 @@ error_info(Reason, Name, From, Msg, State, Debug) ->
end
end;
_ ->
- Reason
+ error_logger:limit_term(Reason)
end,
{ClientFmt, ClientArgs} = client_stacktrace(From),
- format("** Generic server ~p terminating \n"
- "** Last message in was ~p~n"
- "** When Server state == ~p~n"
- "** Reason for termination == ~n** ~p~n" ++ ClientFmt,
- [Name, Msg, State, Reason1] ++ ClientArgs),
+ LimitedState = error_logger:limit_term(State),
+ error_logger:format("** Generic server ~p terminating \n"
+ "** Last message in was ~p~n"
+ "** When Server state == ~p~n"
+ "** Reason for termination == ~n** ~p~n" ++ ClientFmt,
+ [Name, Msg, LimitedState, Reason1] ++ ClientArgs),
sys:print_log(Debug),
ok.
client_stacktrace(undefined) ->
diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl
index 86109f04b4..b5e9da1e66 100644
--- a/lib/stdlib/src/gen_statem.erl
+++ b/lib/stdlib/src/gen_statem.erl
@@ -1722,6 +1722,8 @@ error_info(
end;
_ -> {Reason,Stacktrace}
end,
+ [LimitedP, LimitedFmtData, LimitedFixedReason] =
+ [error_logger:limit_term(D) || D <- [P, FmtData, FixedReason]],
CBMode =
case StateEnter of
true ->
@@ -1755,8 +1757,8 @@ error_info(
[] -> [];
[Event|_] -> [Event]
end] ++
- [FmtData,
- Class,FixedReason,
+ [LimitedFmtData,
+ Class,LimitedFixedReason,
CBMode] ++
case Q of
[_|[_|_] = Events] -> [Events];
@@ -1764,7 +1766,7 @@ error_info(
end ++
case P of
[] -> [];
- _ -> [P]
+ _ -> [LimitedP]
end ++
case FixedStacktrace of
[] -> [];
diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl
index 5ed2f4d888..9d447418f8 100644
--- a/lib/stdlib/src/io_lib.erl
+++ b/lib/stdlib/src/io_lib.erl
@@ -84,6 +84,8 @@
-export([write_unicode_string/1, write_unicode_char/1,
deep_unicode_char_list/1]).
+-export([limit_term/2]).
+
-export_type([chars/0, latin1_string/0, continuation/0,
fread_error/0, fread_item/0, format_spec/0]).
@@ -911,3 +913,116 @@ binrev(L) ->
binrev(L, T) ->
list_to_binary(lists:reverse(L, T)).
+
+-spec limit_term(term(), non_neg_integer()) -> term().
+
+%% The intention is to mimic the depth limitation of io_lib:write()
+%% and io_lib_pretty:print(). The leaves ('...') should never be
+%% seen when printed with the same depth. Bitstrings are never
+%% truncated, which is OK as long as they are not sent to other nodes.
+limit_term(Term, Depth) ->
+ try test_limit(Term, Depth) of
+ ok -> Term
+ catch
+ throw:limit ->
+ limit(Term, Depth)
+ end.
+
+limit(_, 0) -> '...';
+limit([H|T]=L, D) ->
+ if
+ D =:= 1 -> '...';
+ true ->
+ case printable_list(L) of
+ true -> L;
+ false ->
+ [limit(H, D-1)|limit_tail(T, D-1)]
+ end
+ end;
+limit(Term, D) when is_map(Term) ->
+ limit_map(Term, D);
+limit({}=T, _D) -> T;
+limit(T, D) when is_tuple(T) ->
+ if
+ D =:= 1 -> '...';
+ true ->
+ list_to_tuple([limit(element(1, T), D-1)|
+ limit_tail(tl(tuple_to_list(T)), D-1)])
+ end;
+limit(<<_/bitstring>>=Term, D) -> limit_bitstring(Term, D);
+limit(Term, _D) -> Term.
+
+limit_tail([], _D) -> [];
+limit_tail(_, 1) -> ['...'];
+limit_tail([H|T], D) ->
+ [limit(H, D-1)|limit_tail(T, D-1)];
+limit_tail(Other, D) ->
+ limit(Other, D-1).
+
+%% Cannot limit maps properly since there is no guarantee that
+%% maps:from_list() creates a map with the same internal ordering of
+%% the selected associations as in Map.
+limit_map(Map, D) ->
+ maps:from_list(erts_internal:maps_to_list(Map, D)).
+%% maps:from_list(limit_map_body(erts_internal:maps_to_list(Map, D), D)).
+
+%% limit_map_body(_, 0) -> [{'...', '...'}];
+%% limit_map_body([], _) -> [];
+%% limit_map_body([{K,V}], D) -> [limit_map_assoc(K, V, D)];
+%% limit_map_body([{K,V}|KVs], D) ->
+%% [limit_map_assoc(K, V, D) | limit_map_body(KVs, D-1)].
+
+%% limit_map_assoc(K, V, D) ->
+%% {limit(K, D-1), limit(V, D-1)}.
+
+limit_bitstring(B, _D) -> B. %% Keeps all printable binaries.
+
+test_limit(_, 0) -> throw(limit);
+test_limit([H|T]=L, D) when is_integer(D) ->
+ if
+ D =:= 1 -> throw(limit);
+ true ->
+ case printable_list(L) of
+ true -> ok;
+ false ->
+ test_limit(H, D-1),
+ test_limit_tail(T, D-1)
+ end
+ end;
+test_limit(Term, D) when is_map(Term) ->
+ test_limit_map(Term, D);
+test_limit({}, _D) -> ok;
+test_limit(T, D) when is_tuple(T) ->
+ test_limit_tuple(T, 1, tuple_size(T), D);
+test_limit(<<_/bitstring>>=Term, D) -> test_limit_bitstring(Term, D);
+test_limit(_Term, _D) -> ok.
+
+test_limit_tail([], _D) -> ok;
+test_limit_tail(_, 1) -> throw(limit);
+test_limit_tail([H|T], D) ->
+ test_limit(H, D-1),
+ test_limit_tail(T, D-1);
+test_limit_tail(Other, D) ->
+ test_limit(Other, D-1).
+
+test_limit_tuple(_T, I, Sz, _D) when I > Sz -> ok;
+test_limit_tuple(_, _, _, 1) -> throw(limit);
+test_limit_tuple(T, I, Sz, D) ->
+ test_limit(element(I, T), D-1),
+ test_limit_tuple(T, I+1, Sz, D-1).
+
+test_limit_map(_Map, _D) -> ok.
+%% test_limit_map_body(erts_internal:maps_to_list(Map, D), D).
+
+%% test_limit_map_body(_, 0) -> throw(limit);
+%% test_limit_map_body([], _) -> ok;
+%% test_limit_map_body([{K,V}], D) -> test_limit_map_assoc(K, V, D);
+%% test_limit_map_body([{K,V}|KVs], D) ->
+%% test_limit_map_assoc(K, V, D),
+%% test_limit_map_body(KVs, D-1).
+
+%% test_limit_map_assoc(K, V, D) ->
+%% test_limit(K, D-1),
+%% test_limit(V, D-1).
+
+test_limit_bitstring(_, _) -> ok.
diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl
index 14d925bacf..4b2d15c8b3 100644
--- a/lib/stdlib/src/io_lib_format.erl
+++ b/lib/stdlib/src/io_lib_format.erl
@@ -335,7 +335,7 @@ base(B) when is_integer(B) ->
term(T, none, _Adj, none, _Pad) -> T;
term(T, none, Adj, P, Pad) -> term(T, P, Adj, P, Pad);
term(T, F, Adj, P0, Pad) ->
- L = lists:flatlength(T),
+ L = string:length(T),
P = erlang:min(L, case P0 of none -> F; _ -> min(P0, F) end),
if
L > P ->
@@ -675,11 +675,11 @@ cdata_to_chars(B) when is_binary(B) ->
string(S, none, _Adj, none, _Pad) -> S;
string(S, F, Adj, none, Pad) ->
- string_field(S, F, Adj, lists:flatlength(S), Pad);
+ string_field(S, F, Adj, string:length(S), Pad);
string(S, none, _Adj, P, Pad) ->
- string_field(S, P, left, lists:flatlength(S), Pad);
+ string_field(S, P, left, string:length(S), Pad);
string(S, F, Adj, P, Pad) when F >= P ->
- N = lists:flatlength(S),
+ N = string:length(S),
if F > P ->
if N > P ->
adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
@@ -749,18 +749,7 @@ adjust(Data, Pad, right) -> [Pad|Data].
%% Flatten and truncate a deep list to at most N elements.
flat_trunc(List, N) when is_integer(N), N >= 0 ->
- flat_trunc(List, N, [], []).
-
-flat_trunc(L, 0, _, R) when is_list(L) ->
- lists:reverse(R);
-flat_trunc([H|T], N, S, R) when is_list(H) ->
- flat_trunc(H, N, [T|S], R);
-flat_trunc([H|T], N, S, R) ->
- flat_trunc(T, N-1, S, [H|R]);
-flat_trunc([], N, [H|S], R) ->
- flat_trunc(H, N, S, R);
-flat_trunc([], _, [], R) ->
- lists:reverse(R).
+ string:slice(List, 0, N).
%% A deep version of string:chars/2,3
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index ff368d02da..505613b80e 100644
--- a/lib/stdlib/src/io_lib_pretty.erl
+++ b/lib/stdlib/src/io_lib_pretty.erl
@@ -473,7 +473,7 @@ print_length(<<_/bitstring>>=Bin, D, _RF, Enc, Str) ->
print_length(Term, _D, _RF, _Enc, _Str) ->
S = io_lib:write(Term),
%% S can contain unicode, so iolist_size(S) cannot be used here
- {S, lists:flatlength(S)}.
+ {S, string:length(S)}.
print_length_map(_Map, 1, _RF, _Enc, _Str) ->
{"#{...}", 6};
diff --git a/lib/stdlib/src/lib.erl b/lib/stdlib/src/lib.erl
index aa6797bce6..c6eb0d7915 100644
--- a/lib/stdlib/src/lib.erl
+++ b/lib/stdlib/src/lib.erl
@@ -27,7 +27,7 @@
-export([format_exception/6, format_exception/7,
format_stacktrace/4, format_stacktrace/5,
- format_call/4, format_call/5, format_fun/1]).
+ format_call/4, format_call/5, format_fun/1, format_fun/2]).
-spec flush_receive() -> 'ok'.
@@ -400,7 +400,11 @@ format_call(I, ForMForFun, As, FormatFun, Enc)
format_call("", n_spaces(I-1), ForMForFun, As, FormatFun, Enc).
%% -> iolist() (no \n at end)
-format_fun(Fun) when is_function(Fun) ->
+format_fun(Fun) ->
+ format_fun(Fun, latin1).
+
+%% -> iolist() (no \n at end)
+format_fun(Fun, Enc) when is_function(Fun) ->
{module, M} = erlang:fun_info(Fun, module),
{name, F} = erlang:fun_info(Fun, name),
{arity, A} = erlang:fun_info(Fun, arity),
@@ -410,9 +414,9 @@ format_fun(Fun) when is_function(Fun) ->
{type, local} when M =:= erl_eval ->
io_lib:fwrite(<<"interpreted function with arity ~w">>, [A]);
{type, local} ->
- mfa_to_string(M, F, A);
+ mfa_to_string(M, F, A, Enc);
{type, external} ->
- mfa_to_string(M, F, A)
+ mfa_to_string(M, F, A, Enc)
end.
analyze_exception(error, Term, Stack) ->
@@ -454,11 +458,11 @@ explain_reason({badarg,V}, error=Cl, [], PF, S, _Enc) -> % orelse, andalso
format_value(V, <<"bad argument: ">>, Cl, PF, S);
explain_reason(badarith, error, [], _PF, _S, _Enc) ->
<<"an error occurred when evaluating an arithmetic expression">>;
-explain_reason({badarity,{Fun,As}}, error, [], _PF, _S, _Enc)
+explain_reason({badarity,{Fun,As}}, error, [], _PF, _S, Enc)
when is_function(Fun) ->
%% Only the arity is displayed, not the arguments As.
- io_lib:fwrite(<<"~s called with ~s">>,
- [format_fun(Fun), argss(length(As))]);
+ io_lib:fwrite(<<"~ts called with ~s">>,
+ [format_fun(Fun, Enc), argss(length(As))]);
explain_reason({badfun,Term}, error=Cl, [], PF, S, _Enc) ->
format_value(Term, <<"bad function ">>, Cl, PF, S);
explain_reason({badmatch,Term}, error=Cl, [], PF, S, _Enc) ->
@@ -489,14 +493,15 @@ explain_reason({try_clause,V}, error=Cl, [], PF, S, _Enc) ->
%% "there is no try clause with a true guard sequence and a
%% pattern matching..."
format_value(V, <<"no try clause matching ">>, Cl, PF, S);
-explain_reason(undef, error, [{M,F,A,_}], _PF, _S, _Enc) ->
+explain_reason(undef, error, [{M,F,A,_}], _PF, _S, Enc) ->
%% Only the arity is displayed, not the arguments, if there are any.
- io_lib:fwrite(<<"undefined function ~s">>,
- [mfa_to_string(M, F, n_args(A))]);
-explain_reason({shell_undef,F,A,_}, error, [], _PF, _S, _Enc) ->
+ io_lib:fwrite(<<"undefined function ~ts">>,
+ [mfa_to_string(M, F, n_args(A), Enc)]);
+explain_reason({shell_undef,F,A,_}, error, [], _PF, _S, Enc) ->
%% Give nicer reports for undefined shell functions
%% (but not when the user actively calls shell_default:F(...)).
- io_lib:fwrite(<<"undefined shell command ~s/~w">>, [F, n_args(A)]);
+ FS = to_string(F, Enc),
+ io_lib:fwrite(<<"undefined shell command ~ts/~w">>, [FS, n_args(A)]);
%% Exit codes returned by erl_eval only:
explain_reason({argument_limit,_Fun}, error, [], _PF, _S, _Enc) ->
io_lib:fwrite(<<"limit of number of arguments to interpreted function"
@@ -546,17 +551,18 @@ format_stacktrace1(S0, Stack0, PF, SF, Enc) ->
format_stacktrace2(S, Stack, 1, PF, Enc).
format_stacktrace2(S, [{M,F,A,L}|Fs], N, PF, Enc) when is_integer(A) ->
- [io_lib:fwrite(<<"~s~s ~s ~s">>,
+ [io_lib:fwrite(<<"~s~s ~ts ~s">>,
[sep(N, S), origin(N, M, F, A),
- mfa_to_string(M, F, A),
+ mfa_to_string(M, F, A, Enc),
location(L)])
| format_stacktrace2(S, Fs, N + 1, PF, Enc)];
format_stacktrace2(S, [{M,F,As,_}|Fs], N, PF, Enc) when is_list(As) ->
A = length(As),
CalledAs = [S,<<" called as ">>],
C = format_call("", CalledAs, {M,F}, As, PF, Enc),
- [io_lib:fwrite(<<"~s~s ~s\n~s~ts">>,
- [sep(N, S), origin(N, M, F, A), mfa_to_string(M, F, A),
+ [io_lib:fwrite(<<"~s~s ~ts\n~s~ts">>,
+ [sep(N, S), origin(N, M, F, A),
+ mfa_to_string(M, F, A, Enc),
CalledAs, C])
| format_stacktrace2(S, Fs, N + 1, PF, Enc)];
format_stacktrace2(_S, [], _N, _PF, _Enc) ->
@@ -594,10 +600,10 @@ format_call(ErrStr, Pre1, ForMForFun, As, PF, Enc) ->
{yes,Op} ->
format_op(ErrStr, Pre1, Op, As, PF, Enc);
no ->
- MFs = mf_to_string(ForMForFun, Arity),
- I1 = iolist_size([Pre1,ErrStr|MFs]),
+ MFs = mf_to_string(ForMForFun, Arity, Enc),
+ I1 = string:length([Pre1,ErrStr|MFs]),
S1 = pp_arguments(PF, As, I1, Enc),
- S2 = pp_arguments(PF, As, iolist_size([Pre1|MFs]), Enc),
+ S2 = pp_arguments(PF, As, string:length([Pre1|MFs]), Enc),
Long = count_nl(pp_arguments(PF, [a2345,b2345], I1, Enc)) > 0,
case Long or (count_nl(S2) < count_nl(S1)) of
true ->
@@ -656,10 +662,10 @@ printable_list(latin1, As) ->
printable_list(_, As) ->
io_lib:printable_list(As).
-mfa_to_string(M, F, A) ->
- io_lib:fwrite(<<"~s/~w">>, [mf_to_string({M, F}, A), A]).
+mfa_to_string(M, F, A, Enc) ->
+ io_lib:fwrite(<<"~ts/~w">>, [mf_to_string({M, F}, A, Enc), A]).
-mf_to_string({M, F}, A) ->
+mf_to_string({M, F}, A, Enc) ->
case erl_internal:bif(M, F, A) of
true ->
io_lib:fwrite(<<"~w">>, [F]);
@@ -670,13 +676,15 @@ mf_to_string({M, F}, A) ->
{yes, F} ->
atom_to_list(F);
no ->
- io_lib:fwrite(<<"~w:~w">>, [M, F])
+ FS = to_string(F, Enc),
+ io_lib:fwrite(<<"~w:~ts">>, [M, FS])
end
end;
-mf_to_string(Fun, _A) when is_function(Fun) ->
- format_fun(Fun);
-mf_to_string(F, _A) ->
- io_lib:fwrite(<<"~w">>, [F]).
+mf_to_string(Fun, _A, Enc) when is_function(Fun) ->
+ format_fun(Fun, Enc);
+mf_to_string(F, _A, Enc) ->
+ FS = to_string(F, Enc),
+ io_lib:fwrite(<<"~ts">>, [FS]).
format_value(V, ErrStr, Class, PF, S) ->
Pre1Sz = exited_size(Class),
@@ -725,9 +733,14 @@ exited(exit) ->
exited(throw) ->
<<"exception throw: ">>.
+to_string(A, latin1) ->
+ io_lib:write_atom_as_latin1(A);
+to_string(A, _) ->
+ io_lib:write_atom(A).
+
size(latin1, S) ->
{iolist_size(S),S};
size(_, S0) ->
S = unicode:characters_to_list(S0, unicode),
true = is_list(S),
- {length(S),S}.
+ {string:length(S),S}.
diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl
index 98745b13f3..6616e957c0 100644
--- a/lib/stdlib/src/ms_transform.erl
+++ b/lib/stdlib/src/ms_transform.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -91,12 +91,12 @@ format_error(?ERR_GUARDMATCH) ->
"fun with guard matching ('=' in guard) is illegal as match_spec as well";
format_error({?ERR_GUARDLOCALCALL, Name, Arithy}) ->
lists:flatten(io_lib:format("fun containing the local function call "
- "'~w/~w' (called in guard) "
+ "'~tw/~w' (called in guard) "
"cannot be translated into match_spec",
[Name, Arithy]));
format_error({?ERR_GUARDREMOTECALL, Module, Name, Arithy}) ->
lists:flatten(io_lib:format("fun containing the remote function call "
- "'~w:~w/~w' (called in guard) "
+ "'~w:~tw/~w' (called in guard) "
"cannot be translated into match_spec",
[Module,Name,Arithy]));
format_error({?ERR_GUARDELEMENT, Str}) ->
@@ -117,12 +117,12 @@ format_error(?ERR_BODYMATCH) ->
"fun with body matching ('=' in body) is illegal as match_spec";
format_error({?ERR_BODYLOCALCALL, Name, Arithy}) ->
lists:flatten(io_lib:format("fun containing the local function "
- "call '~w/~w' (called in body) "
+ "call '~tw/~w' (called in body) "
"cannot be translated into match_spec",
[Name,Arithy]));
format_error({?ERR_BODYREMOTECALL, Module, Name, Arithy}) ->
lists:flatten(io_lib:format("fun containing the remote function call "
- "'~w:~w/~w' (called in body) "
+ "'~w:~tw/~w' (called in body) "
"cannot be translated into match_spec",
[Module,Name,Arithy]));
format_error({?ERR_BODYELEMENT, Str}) ->
@@ -147,15 +147,15 @@ format_error({?ERR_UNBOUND_VARIABLE, Str}) ->
"into match_spec", [Str]));
format_error({?ERR_HEADBADREC,Name}) ->
lists:flatten(
- io_lib:format("fun head contains unknown record type ~w",[Name]));
+ io_lib:format("fun head contains unknown record type ~tw",[Name]));
format_error({?ERR_HEADBADFIELD,RName,FName}) ->
lists:flatten(
- io_lib:format("fun head contains reference to unknown field ~w in "
- "record type ~w",[FName, RName]));
+ io_lib:format("fun head contains reference to unknown field ~tw in "
+ "record type ~tw",[FName, RName]));
format_error({?ERR_HEADMULTIFIELD,RName,FName}) ->
lists:flatten(
- io_lib:format("fun head contains already defined field ~w in "
- "record type ~w",[FName, RName]));
+ io_lib:format("fun head contains already defined field ~tw in "
+ "record type ~tw",[FName, RName]));
format_error({?ERR_HEADDOLLARATOM,Atom}) ->
lists:flatten(
io_lib:format("fun head contains atom ~w, which conflics with reserved "
@@ -166,28 +166,28 @@ format_error({?ERR_HEADBINMATCH,Atom}) ->
"which cannot be translated into match_spec", [Atom]));
format_error({?ERR_GUARDBADREC,Name}) ->
lists:flatten(
- io_lib:format("fun guard contains unknown record type ~w",[Name]));
+ io_lib:format("fun guard contains unknown record type ~tw",[Name]));
format_error({?ERR_GUARDBADFIELD,RName,FName}) ->
lists:flatten(
- io_lib:format("fun guard contains reference to unknown field ~w in "
- "record type ~w",[FName, RName]));
+ io_lib:format("fun guard contains reference to unknown field ~tw in "
+ "record type ~tw",[FName, RName]));
format_error({?ERR_GUARDMULTIFIELD,RName,FName}) ->
lists:flatten(
- io_lib:format("fun guard contains already defined field ~w in "
- "record type ~w",[FName, RName]));
+ io_lib:format("fun guard contains already defined field ~tw in "
+ "record type ~tw",[FName, RName]));
format_error({?ERR_BODYBADREC,Name}) ->
lists:flatten(
- io_lib:format("fun body contains unknown record type ~w",[Name]));
+ io_lib:format("fun body contains unknown record type ~tw",[Name]));
format_error({?ERR_BODYBADFIELD,RName,FName}) ->
lists:flatten(
- io_lib:format("fun body contains reference to unknown field ~w in "
- "record type ~w",[FName, RName]));
+ io_lib:format("fun body contains reference to unknown field ~tw in "
+ "record type ~tw",[FName, RName]));
format_error({?ERR_BODYMULTIFIELD,RName,FName}) ->
lists:flatten(
- io_lib:format("fun body contains already defined field ~w in "
- "record type ~w",[FName, RName]));
+ io_lib:format("fun body contains already defined field ~tw in "
+ "record type ~tw",[FName, RName]));
format_error(Else) ->
- lists:flatten(io_lib:format("Unknown error code ~w",[Else])).
+ lists:flatten(io_lib:format("Unknown error code ~tw",[Else])).
%%
%% Called when translating in shell
@@ -501,10 +501,20 @@ tg0(Line,[H|T],B) ->
tg({match,Line,_,_},B) ->
throw({error,Line,?ERR_GENMATCH+B#tgd.eb});
-tg({op, Line, Operator, O1, O2}, B) ->
- {tuple, Line, [{atom, Line, Operator}, tg(O1,B), tg(O2,B)]};
-tg({op, Line, Operator, O1}, B) ->
- {tuple, Line, [{atom, Line, Operator}, tg(O1,B)]};
+tg({op, Line, Operator, O1, O2}=Expr, B) ->
+ case erl_eval:partial_eval(Expr) of
+ Expr ->
+ {tuple, Line, [{atom, Line, Operator}, tg(O1, B), tg(O2, B)]};
+ Value ->
+ Value
+ end;
+tg({op, Line, Operator, O1}=Expr, B) ->
+ case erl_eval:partial_eval(Expr) of
+ Expr ->
+ {tuple, Line, [{atom, Line, Operator}, tg(O1, B)]};
+ Value ->
+ Value
+ end;
tg({call, _Line, {atom, Line2, bindings},[]},_B) ->
{atom, Line2, '$*'};
tg({call, _Line, {atom, Line2, object},[]},_B) ->
@@ -723,7 +733,7 @@ tg(T,B) when is_tuple(T), tuple_size(T) >= 2 ->
throw({error,Line,{?ERR_GENELEMENT+B#tgd.eb,
translate_language_element(Element)}});
tg(Other,B) ->
- Element = io_lib:format("unknown element ~w", [Other]),
+ Element = io_lib:format("unknown element ~tw", [Other]),
throw({error,unknown,{?ERR_GENELEMENT+B#tgd.eb,Element}}).
transform_head([V],OuterBound) ->
diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl
index 2219467a8d..d4d1bdccec 100644
--- a/lib/stdlib/src/proc_lib.erl
+++ b/lib/stdlib/src/proc_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -519,7 +519,8 @@ my_info_1(Class, Reason, Stacktrace) ->
get_process_info(self(), registered_name),
{error_info, {Class,Reason,Stacktrace}},
get_ancestors(self()),
- get_process_info(self(), messages),
+ get_process_info(self(), message_queue_len),
+ get_messages(self()),
get_process_info(self(), links),
get_cleaned_dictionary(self()),
get_process_info(self(), trap_exit),
@@ -539,12 +540,49 @@ get_ancestors(Pid) ->
{ancestors,[]}
end.
+%% The messages and the dictionary are possibly limited too much if
+%% some error handles output the messages or the dictionary using ~P
+%% or ~W with depth greater than the depth used here (the depth of
+%% control characters P and W takes precedence over the depth set by
+%% application variable error_logger_format_depth). However, it is
+%% assumed that all report handlers call proc_lib:format().
+get_messages(Pid) ->
+ Messages = get_process_messages(Pid),
+ {messages, error_logger:limit_term(Messages)}.
+
+get_process_messages(Pid) ->
+ Depth = error_logger:get_format_depth(),
+ case Pid =/= self() orelse Depth =:= unlimited of
+ true ->
+ {messages, Messages} = get_process_info(Pid, messages),
+ Messages;
+ false ->
+ %% If there are more messages than Depth, garbage
+ %% collection can sometimes be avoided by collecting just
+ %% enough messages for the crash report. It is assumed the
+ %% process is about to die anyway.
+ receive_messages(Depth)
+ end.
+
+receive_messages(0) -> [];
+receive_messages(N) ->
+ receive
+ M ->
+ [M|receive_messages(N - 1)]
+ after 0 ->
+ []
+ end.
+
get_cleaned_dictionary(Pid) ->
case get_process_info(Pid,dictionary) of
- {dictionary,Dict} -> {dictionary,clean_dict(Dict)};
+ {dictionary,Dict} -> {dictionary,cleaned_dict(Dict)};
_ -> {dictionary,[]}
end.
+cleaned_dict(Dict) ->
+ CleanDict = clean_dict(Dict),
+ error_logger:limit_term(CleanDict).
+
clean_dict([{'$ancestors',_}|Dict]) ->
clean_dict(Dict);
clean_dict([{'$initial_call',_}|Dict]) ->
@@ -582,20 +620,24 @@ make_neighbour_reports1([P|Ps]) ->
make_neighbour_reports1([]) ->
[].
+%% Do not include messages or process dictionary, even if
+%% error_logger_format_depth is unlimited.
make_neighbour_report(Pid) ->
[{pid, Pid},
get_process_info(Pid, registered_name),
get_initial_call(Pid),
get_process_info(Pid, current_function),
get_ancestors(Pid),
- get_process_info(Pid, messages),
+ get_process_info(Pid, message_queue_len),
+ %% get_messages(Pid),
get_process_info(Pid, links),
- get_cleaned_dictionary(Pid),
+ %% get_cleaned_dictionary(Pid),
get_process_info(Pid, trap_exit),
get_process_info(Pid, status),
get_process_info(Pid, heap_size),
get_process_info(Pid, stack_size),
- get_process_info(Pid, reductions)
+ get_process_info(Pid, reductions),
+ get_process_info(Pid, current_stacktrace)
].
get_initial_call(Pid) ->
@@ -722,24 +764,39 @@ format(CrashReport, Encoding) ->
format([OwnReport,LinkReport], Encoding, Depth) ->
Extra = {Encoding,Depth},
- OwnFormat = format_report(OwnReport, Extra),
- LinkFormat = format_report(LinkReport, Extra),
+ MyIndent = " ",
+ OwnFormat = format_report(OwnReport, MyIndent, Extra),
+ LinkFormat = format_link_report(LinkReport, MyIndent, Extra),
Str = io_lib:format(" crasher:~n~ts neighbours:~n~ts",
[OwnFormat, LinkFormat]),
lists:flatten(Str).
-format_report(Rep, Extra) when is_list(Rep) ->
- format_rep(Rep, Extra);
-format_report(Rep, {Enc,_}) ->
- io_lib:format("~"++modifier(Enc)++"p~n", [Rep]).
-
-format_rep([{initial_call,InitialCall}|Rep], {_Enc,Depth}=Extra) ->
- [format_mfa(InitialCall, Depth)|format_rep(Rep, Extra)];
-format_rep([{error_info,{Class,Reason,StackTrace}}|Rep], Extra) ->
- [format_exception(Class, Reason, StackTrace, Extra)|format_rep(Rep, Extra)];
-format_rep([{Tag,Data}|Rep], Extra) ->
- [format_tag(Tag, Data, Extra)|format_rep(Rep, Extra)];
-format_rep(_, _Extra) ->
+format_link_report([Link|Reps], Indent, Extra) ->
+ Rep = case Link of
+ {neighbour,Rep0} -> Rep0;
+ _ -> Link
+ end,
+ LinkIndent = [" ",Indent],
+ [Indent,"neighbour:\n",format_report(Rep, LinkIndent, Extra)|
+ format_link_report(Reps, Indent, Extra)];
+format_link_report(Rep, Indent, Extra) ->
+ format_report(Rep, Indent, Extra).
+
+format_report(Rep, Indent, Extra) when is_list(Rep) ->
+ format_rep(Rep, Indent, Extra);
+format_report(Rep, Indent, {Enc,unlimited}) ->
+ io_lib:format("~s~"++modifier(Enc)++"p~n", [Indent, Rep]);
+format_report(Rep, Indent, {Enc,Depth}) ->
+ io_lib:format("~s~"++modifier(Enc)++"P~n", [Indent, Rep, Depth]).
+
+format_rep([{initial_call,InitialCall}|Rep], Indent, Extra) ->
+ [format_mfa(Indent, InitialCall, Extra)|format_rep(Rep, Indent, Extra)];
+format_rep([{error_info,{Class,Reason,StackTrace}}|Rep], Indent, Extra) ->
+ [format_exception(Class, Reason, StackTrace, Extra)|
+ format_rep(Rep, Indent, Extra)];
+format_rep([{Tag,Data}|Rep], Indent, Extra) ->
+ [format_tag(Indent, Tag, Data, Extra)|format_rep(Rep, Indent, Extra)];
+format_rep(_, _, _Extra) ->
[].
format_exception(Class, Reason, StackTrace, {Enc,_}=Extra) ->
@@ -750,16 +807,21 @@ format_exception(Class, Reason, StackTrace, {Enc,_}=Extra) ->
[EI, lib:format_exception(1+length(EI), Class, Reason,
StackTrace, StackFun, PF, Enc), "\n"].
-format_mfa({M,F,Args}=StartF, Depth) ->
+format_mfa(Indent, {M,F,Args}=StartF, {Enc,_}=Extra) ->
try
A = length(Args),
- [" initial call: ",atom_to_list(M),$:,atom_to_list(F),$/,
+ [Indent,"initial call: ",atom_to_list(M),$:,to_string(F, Enc),$/,
integer_to_list(A),"\n"]
catch
error:_ ->
- format_tag(initial_call, StartF, Depth)
+ format_tag(Indent, initial_call, StartF, Extra)
end.
+to_string(A, latin1) ->
+ io_lib:write_atom_as_latin1(A);
+to_string(A, _) ->
+ io_lib:write_atom(A).
+
pp_fun({Enc,Depth}) ->
{Letter,Tl} = case Depth of
unlimited -> {"p",[]};
@@ -770,12 +832,12 @@ pp_fun({Enc,Depth}) ->
io_lib:format("~." ++ integer_to_list(I) ++ P, [Term|Tl])
end.
-format_tag(Tag, Data, {_Enc,Depth}) ->
+format_tag(Indent, Tag, Data, {_Enc,Depth}) ->
case Depth of
unlimited ->
- io_lib:format(" ~p: ~80.18p~n", [Tag, Data]);
+ io_lib:format("~s~p: ~80.18p~n", [Indent, Tag, Data]);
_ ->
- io_lib:format(" ~p: ~80.18P~n", [Tag, Data, Depth])
+ io_lib:format("~s~p: ~80.18P~n", [Indent, Tag, Data, Depth])
end.
modifier(latin1) -> "";
diff --git a/lib/stdlib/src/shell.erl b/lib/stdlib/src/shell.erl
index 76a2789406..26b3960f4f 100644
--- a/lib/stdlib/src/shell.erl
+++ b/lib/stdlib/src/shell.erl
@@ -727,7 +727,7 @@ result_will_be_saved() ->
used_record_defs(E, RT) ->
%% Be careful to return a list where used records come before
%% records that use them. The linter wants them ordered that way.
- UR = case used_records(E, [], RT) of
+ UR = case used_records(E, [], RT, []) of
[] ->
[];
L0 ->
@@ -737,13 +737,19 @@ used_record_defs(E, RT) ->
end,
record_defs(RT, UR).
-used_records(E, U0, RT) ->
+used_records(E, U0, RT, Skip) ->
case used_records(E) of
{name,Name,E1} ->
- U = used_records(ets:lookup(RT, Name), [Name | U0], RT),
- used_records(E1, U, RT);
+ U = case lists:member(Name, Skip) of
+ true ->
+ U0;
+ false ->
+ R = ets:lookup(RT, Name),
+ used_records(R, [Name | U0], RT, [Name | Skip])
+ end,
+ used_records(E1, U, RT, Skip);
{expr,[E1 | Es]} ->
- used_records(Es, used_records(E1, U0, RT), RT);
+ used_records(Es, used_records(E1, U0, RT, Skip), RT, Skip);
_ ->
U0
end.
@@ -1238,22 +1244,22 @@ read_file_records(File, Opts) ->
end.
%% This is how the debugger searches for source files. See int.erl.
-try_source(Beam, CB) ->
- Os = case lists:keyfind(options, 1, binary_to_term(CB)) of
- false -> [];
- {_, Os0} -> Os0
- end,
+try_source(Beam, RawCB) ->
+ EbinDir = filename:dirname(Beam),
+ CB = binary_to_term(RawCB),
+ Os = proplists:get_value(options,CB, []),
Src0 = filename:rootname(Beam) ++ ".erl",
- case is_file(Src0) of
- true -> parse_file(Src0, Os);
- false ->
- EbinDir = filename:dirname(Beam),
- Src = filename:join([filename:dirname(EbinDir), "src",
- filename:basename(Src0)]),
- case is_file(Src) of
- true -> parse_file(Src, Os);
- false -> {error, nofile}
- end
+ Src1 = filename:join([filename:dirname(EbinDir), "src",
+ filename:basename(Src0)]),
+ Src2 = proplists:get_value(source, CB, []),
+ try_sources([Src0,Src1,Src2], Os).
+
+try_sources([], _) ->
+ {error, nofile};
+try_sources([Src|Rest], Os) ->
+ case is_file(Src) of
+ true -> parse_file(Src, Os);
+ false -> try_sources(Rest, Os)
end.
is_file(Name) ->
diff --git a/lib/stdlib/src/string.erl b/lib/stdlib/src/string.erl
index 17135dd64a..4972da297d 100644
--- a/lib/stdlib/src/string.erl
+++ b/lib/stdlib/src/string.erl
@@ -384,7 +384,7 @@ to_float(String) ->
end.
to_number(String, Number, Rest, List, _Tail) when is_binary(String) ->
- BSz = length(List)-length(Rest),
+ BSz = erlang:length(List)-erlang:length(Rest),
<<_:BSz/binary, Cont/binary>> = String,
{Number, Cont};
to_number(_, Number, Rest, _, Tail) ->
@@ -486,12 +486,14 @@ find(String, SearchPattern, trailing) ->
%% Fetch first codepoint and return rest in tail
-spec next_grapheme(String::unicode:chardata()) ->
- maybe_improper_list(grapheme_cluster(),unicode:chardata()).
+ maybe_improper_list(grapheme_cluster(),unicode:chardata()) |
+ {error,unicode:chardata()}.
next_grapheme(CD) -> unicode_util:gc(CD).
%% Fetch first grapheme cluster and return rest in tail
-spec next_codepoint(String::unicode:chardata()) ->
- maybe_improper_list(char(),unicode:chardata()).
+ maybe_improper_list(char(),unicode:chardata()) |
+ {error,unicode:chardata()}.
next_codepoint(CD) -> unicode_util:cp(CD).
%% Internals
@@ -508,7 +510,7 @@ equal_1(A0,B0) ->
case {unicode_util:cp(A0), unicode_util:cp(B0)} of
{[CP|A],[CP|B]} -> equal_1(A,B);
{[], []} -> true;
- _ -> false
+ {L1,L2} when is_list(L1), is_list(L2) -> false
end.
equal_nocase(A, A) -> true;
@@ -517,7 +519,7 @@ equal_nocase(A0, B0) ->
unicode_util:cp(unicode_util:casefold(B0))} of
{[CP|A],[CP|B]} -> equal_nocase(A,B);
{[], []} -> true;
- _ -> false
+ {L1,L2} when is_list(L1), is_list(L2) -> false
end.
equal_norm(A, A, _Norm) -> true;
@@ -526,7 +528,7 @@ equal_norm(A0, B0, Norm) ->
unicode_util:cp(unicode_util:Norm(B0))} of
{[CP|A],[CP|B]} -> equal_norm(A,B, Norm);
{[], []} -> true;
- _ -> false
+ {L1,L2} when is_list(L1), is_list(L2) -> false
end.
equal_norm_nocase(A, A, _Norm) -> true;
@@ -535,7 +537,7 @@ equal_norm_nocase(A0, B0, Norm) ->
unicode_util:cp(unicode_util:casefold(unicode_util:Norm(B0)))} of
{[CP|A],[CP|B]} -> equal_norm_nocase(A,B, Norm);
{[], []} -> true;
- _ -> false
+ {L1,L2} when is_list(L1), is_list(L2) -> false
end.
reverse_1(CD, Acc) ->
@@ -1342,7 +1344,7 @@ bin_search_str(Bin0, Start, Cont, [CP|_]=SearchCPs) ->
String :: string(),
Length :: non_neg_integer().
-len(S) -> length(S).
+len(S) -> erlang:length(S).
%% equal(String1, String2)
%% Test if 2 strings are equal.
@@ -1687,7 +1689,7 @@ left(String, Len) when is_integer(Len) -> left(String, Len, $\s).
Character :: char().
left(String, Len, Char) when is_integer(Char) ->
- Slen = length(String),
+ Slen = erlang:length(String),
if
Slen > Len -> substr(String, 1, Len);
Slen < Len -> l_pad(String, Len-Slen, Char);
@@ -1712,7 +1714,7 @@ right(String, Len) when is_integer(Len) -> right(String, Len, $\s).
Character :: char().
right(String, Len, Char) when is_integer(Char) ->
- Slen = length(String),
+ Slen = erlang:length(String),
if
Slen > Len -> substr(String, Slen-Len+1);
Slen < Len -> r_pad(String, Len-Slen, Char);
@@ -1739,7 +1741,7 @@ centre(String, Len) when is_integer(Len) -> centre(String, Len, $\s).
centre(String, 0, Char) when is_list(String), is_integer(Char) ->
[]; % Strange cases to centre string
centre(String, Len, Char) when is_integer(Char) ->
- Slen = length(String),
+ Slen = erlang:length(String),
if
Slen > Len -> substr(String, (Slen-Len) div 2 + 1, Len);
Slen < Len ->
diff --git a/lib/stdlib/src/sys.erl b/lib/stdlib/src/sys.erl
index a6ecf03716..1f966411c5 100644
--- a/lib/stdlib/src/sys.erl
+++ b/lib/stdlib/src/sys.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -525,7 +525,7 @@ debug_cmd({log_to_file, false}, Debug) ->
{ok, NDebug};
debug_cmd({log_to_file, FileName}, Debug) ->
NDebug = close_log_file(Debug),
- case file:open(FileName, [write]) of
+ case file:open(FileName, [write,{encoding,utf8}]) of
{ok, Fd} ->
{ok, install_debug(log_to_file, Fd, NDebug)};
_Error ->
@@ -648,7 +648,7 @@ debug_options([{log, N} | T], Debug) when is_integer(N), N > 0 ->
debug_options([statistics | T], Debug) ->
debug_options(T, install_debug(statistics, init_stat(), Debug));
debug_options([{log_to_file, FileName} | T], Debug) ->
- case file:open(FileName, [write]) of
+ case file:open(FileName, [write,{encoding,utf8}]) of
{ok, Fd} ->
debug_options(T, install_debug(log_to_file, Fd, Debug));
_Error ->
diff --git a/lib/stdlib/src/unicode.erl b/lib/stdlib/src/unicode.erl
index aa1da400ce..fbe8a94074 100644
--- a/lib/stdlib/src/unicode.erl
+++ b/lib/stdlib/src/unicode.erl
@@ -250,89 +250,110 @@ encoding_to_bom(latin1) ->
-define(GC_N, 200). %% arbitrary number
%% Canonical decompose string to list of chars
--spec characters_to_nfd_list(chardata()) -> [char()].
+-spec characters_to_nfd_list(chardata()) -> [char()] | {error, [char()], chardata()}.
characters_to_nfd_list(CD) ->
+ characters_to_nfd_list(CD, []).
+characters_to_nfd_list(CD, Acc) ->
case unicode_util:nfd(CD) of
- [GC|Str] when is_list(GC) -> GC++characters_to_nfd_list(Str);
- [CP|Str] -> [CP|characters_to_nfd_list(Str)];
- [] -> []
+ [GC|Str] when is_list(GC) -> characters_to_nfd_list(Str, lists:reverse(GC, Acc));
+ [CP|Str] -> characters_to_nfd_list(Str, [CP | Acc]);
+ [] -> lists:reverse(Acc);
+ {error,Error} -> {error, lists:reverse(Acc), Error}
end.
--spec characters_to_nfd_binary(chardata()) -> unicode_binary().
+-spec characters_to_nfd_binary(chardata()) -> unicode_binary() | {error, unicode_binary(), chardata()}.
characters_to_nfd_binary(CD) ->
- list_to_binary(characters_to_nfd_binary(CD, ?GC_N, [])).
+ characters_to_nfd_binary(CD, ?GC_N, [], []).
-characters_to_nfd_binary(CD, N, Row) when N > 0 ->
+characters_to_nfd_binary(CD, N, Row, Acc) when N > 0 ->
case unicode_util:nfd(CD) of
- [GC|Str] -> characters_to_nfd_binary(Str, N-1, [GC|Row]);
- [] -> [characters_to_binary(lists:reverse(Row))]
+ [GC|Str] -> characters_to_nfd_binary(Str, N-1, [GC|Row], Acc);
+ [] -> acc_to_binary(prepend_row_to_acc(Row, Acc));
+ {error, Error} -> {error, acc_to_binary(prepend_row_to_acc(Row, Acc)), Error}
end;
-characters_to_nfd_binary(CD, _, Row) ->
- [characters_to_binary(lists:reverse(Row))|characters_to_nfd_binary(CD,?GC_N,[])].
+characters_to_nfd_binary(CD, _, Row, Acc) ->
+ characters_to_nfd_binary(CD, ?GC_N, [], prepend_row_to_acc(Row, Acc)).
%% Compability Canonical decompose string to list of chars.
--spec characters_to_nfkd_list(chardata()) -> [char()].
+-spec characters_to_nfkd_list(chardata()) -> [char()] | {error, [char()], chardata()}.
characters_to_nfkd_list(CD) ->
+ characters_to_nfkd_list(CD, []).
+characters_to_nfkd_list(CD, Acc) ->
case unicode_util:nfkd(CD) of
- [GC|Str] when is_list(GC) -> GC++characters_to_nfkd_list(Str);
- [CP|Str] -> [CP|characters_to_nfkd_list(Str)];
- [] -> []
+ [GC|Str] when is_list(GC) -> characters_to_nfkd_list(Str, lists:reverse(GC, Acc));
+ [CP|Str] -> characters_to_nfkd_list(Str, [CP | Acc]);
+ [] -> lists:reverse(Acc);
+ {error,Error} -> {error, lists:reverse(Acc), Error}
end.
--spec characters_to_nfkd_binary(chardata()) -> unicode_binary().
+-spec characters_to_nfkd_binary(chardata()) -> unicode_binary() | {error, unicode_binary(), chardata()}.
characters_to_nfkd_binary(CD) ->
- list_to_binary(characters_to_nfkd_binary(CD, ?GC_N, [])).
+ characters_to_nfkd_binary(CD, ?GC_N, [], []).
-characters_to_nfkd_binary(CD, N, Row) when N > 0 ->
+characters_to_nfkd_binary(CD, N, Row, Acc) when N > 0 ->
case unicode_util:nfkd(CD) of
- [GC|Str] -> characters_to_nfkd_binary(Str, N-1, [GC|Row]);
- [] -> [characters_to_binary(lists:reverse(Row))]
+ [GC|Str] -> characters_to_nfkd_binary(Str, N-1, [GC|Row], Acc);
+ [] -> acc_to_binary(prepend_row_to_acc(Row, Acc));
+ {error, Error} -> {error, acc_to_binary(prepend_row_to_acc(Row, Acc)), Error}
end;
-characters_to_nfkd_binary(CD, _, Row) ->
- [characters_to_binary(lists:reverse(Row))|characters_to_nfkd_binary(CD,?GC_N,[])].
+characters_to_nfkd_binary(CD, _, Row, Acc) ->
+ characters_to_nfkd_binary(CD, ?GC_N, [], prepend_row_to_acc(Row, Acc)).
%% Canonical compose string to list of chars
--spec characters_to_nfc_list(chardata()) -> [char()].
+-spec characters_to_nfc_list(chardata()) -> [char()] | {error, [char()], chardata()}.
characters_to_nfc_list(CD) ->
+ characters_to_nfc_list(CD, []).
+characters_to_nfc_list(CD, Acc) ->
case unicode_util:nfc(CD) of
- [CPs|Str] when is_list(CPs) -> CPs ++ characters_to_nfc_list(Str);
- [CP|Str] -> [CP|characters_to_nfc_list(Str)];
- [] -> []
+ [GC|Str] when is_list(GC) -> characters_to_nfc_list(Str, lists:reverse(GC, Acc));
+ [CP|Str] -> characters_to_nfc_list(Str, [CP | Acc]);
+ [] -> lists:reverse(Acc);
+ {error,Error} -> {error, lists:reverse(Acc), Error}
end.
--spec characters_to_nfc_binary(chardata()) -> unicode_binary().
+-spec characters_to_nfc_binary(chardata()) -> unicode_binary() | {error, unicode_binary(), chardata()}.
characters_to_nfc_binary(CD) ->
- list_to_binary(characters_to_nfc_binary(CD, ?GC_N, [])).
+ characters_to_nfc_binary(CD, ?GC_N, [], []).
-characters_to_nfc_binary(CD, N, Row) when N > 0 ->
+characters_to_nfc_binary(CD, N, Row, Acc) when N > 0 ->
case unicode_util:nfc(CD) of
- [GC|Str] -> characters_to_nfc_binary(Str, N-1, [GC|Row]);
- [] -> [characters_to_binary(lists:reverse(Row))]
+ [GC|Str] -> characters_to_nfc_binary(Str, N-1, [GC|Row], Acc);
+ [] -> acc_to_binary(prepend_row_to_acc(Row, Acc));
+ {error, Error} -> {error, acc_to_binary(prepend_row_to_acc(Row, Acc)), Error}
end;
-characters_to_nfc_binary(CD, _, Row) ->
- [characters_to_binary(lists:reverse(Row))|characters_to_nfc_binary(CD,?GC_N,[])].
+characters_to_nfc_binary(CD, _, Row, Acc) ->
+ characters_to_nfc_binary(CD, ?GC_N, [], prepend_row_to_acc(Row, Acc)).
%% Compability Canonical compose string to list of chars
--spec characters_to_nfkc_list(chardata()) -> [char()].
+-spec characters_to_nfkc_list(chardata()) -> [char()] | {error, [char()], chardata()}.
characters_to_nfkc_list(CD) ->
+ characters_to_nfkc_list(CD, []).
+characters_to_nfkc_list(CD, Acc) ->
case unicode_util:nfkc(CD) of
- [CPs|Str] when is_list(CPs) -> CPs ++ characters_to_nfkc_list(Str);
- [CP|Str] -> [CP|characters_to_nfkc_list(Str)];
- [] -> []
+ [GC|Str] when is_list(GC) -> characters_to_nfkc_list(Str, lists:reverse(GC, Acc));
+ [CP|Str] -> characters_to_nfkc_list(Str, [CP | Acc]);
+ [] -> lists:reverse(Acc);
+ {error,Error} -> {error, lists:reverse(Acc), Error}
end.
--spec characters_to_nfkc_binary(chardata()) -> unicode_binary().
+-spec characters_to_nfkc_binary(chardata()) -> unicode_binary() | {error, unicode_binary(), chardata()}.
characters_to_nfkc_binary(CD) ->
- list_to_binary(characters_to_nfkc_binary(CD, ?GC_N, [])).
+ characters_to_nfkc_binary(CD, ?GC_N, [], []).
-characters_to_nfkc_binary(CD, N, Row) when N > 0 ->
+characters_to_nfkc_binary(CD, N, Row, Acc) when N > 0 ->
case unicode_util:nfkc(CD) of
- [GC|Str] -> characters_to_nfkc_binary(Str, N-1, [GC|Row]);
- [] -> [characters_to_binary(lists:reverse(Row))]
+ [GC|Str] -> characters_to_nfkc_binary(Str, N-1, [GC|Row], Acc);
+ [] -> acc_to_binary(prepend_row_to_acc(Row, Acc));
+ {error, Error} -> {error, acc_to_binary(prepend_row_to_acc(Row, Acc)), Error}
end;
-characters_to_nfkc_binary(CD, _, Row) ->
- [characters_to_binary(lists:reverse(Row))|characters_to_nfkc_binary(CD,?GC_N,[])].
+characters_to_nfkc_binary(CD, _, Row, Acc) ->
+ characters_to_nfkc_binary(CD, ?GC_N, [], prepend_row_to_acc(Row, Acc)).
+
+acc_to_binary(Acc) ->
+ list_to_binary(lists:reverse(Acc)).
+prepend_row_to_acc(Row, Acc) ->
+ [characters_to_binary(lists:reverse(Row))|Acc].
%% internals
diff --git a/lib/stdlib/test/c_SUITE.erl b/lib/stdlib/test/c_SUITE.erl
index 4bd32a30f8..f01988478c 100644
--- a/lib/stdlib/test/c_SUITE.erl
+++ b/lib/stdlib/test/c_SUITE.erl
@@ -21,7 +21,9 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2]).
-export([c_1/1, c_2/1, c_3/1, c_4/1, nc_1/1, nc_2/1, nc_3/1, nc_4/1,
- ls/1, memory/1]).
+ c_default_outdir_1/1, c_default_outdir_2/1,
+ nc_default_outdir_1/1, nc_default_outdir_2/1,
+ ls/1, memory/1]).
-include_lib("common_test/include/ct.hrl").
@@ -30,7 +32,10 @@
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
- [c_1, c_2, c_3, c_4, nc_1, nc_2, nc_3, nc_4, ls, memory].
+ [c_1, c_2, c_3, c_4, nc_1, nc_2, nc_3, nc_4,
+ c_default_outdir_1, c_default_outdir_2,
+ nc_default_outdir_1, nc_default_outdir_2,
+ ls, memory].
groups() ->
[].
@@ -124,6 +129,50 @@ nc_4(Config) when is_list(Config) ->
Result = nc(R,[{outdir,W}]),
{ok, m} = Result.
+c_default_outdir_1(Config) ->
+ R = filename:join(proplists:get_value(data_dir, Config), "m.erl"),
+ W = proplists:get_value(priv_dir, Config),
+ file:set_cwd(W),
+ Obj = "m" ++ code:objfile_extension(),
+ _ = file:delete(Obj),
+ false = filelib:is_file(Obj),
+ Result = c:c(R),
+ {ok, m} = Result,
+ true = filelib:is_file(Obj).
+
+c_default_outdir_2(Config) ->
+ R = filename:join(proplists:get_value(data_dir, Config), "m"),
+ W = proplists:get_value(priv_dir, Config),
+ file:set_cwd(W),
+ Obj = "m" ++ code:objfile_extension(),
+ _ = file:delete(Obj),
+ false = filelib:is_file(Obj),
+ Result = c:c(R),
+ {ok, m} = Result,
+ true = filelib:is_file(Obj).
+
+nc_default_outdir_1(Config) ->
+ R = filename:join(proplists:get_value(data_dir, Config), "m.erl"),
+ W = proplists:get_value(priv_dir, Config),
+ file:set_cwd(W),
+ Obj = "m" ++ code:objfile_extension(),
+ _ = file:delete(Obj),
+ false = filelib:is_file(Obj),
+ Result = c:nc(R),
+ {ok, m} = Result,
+ true = filelib:is_file(Obj).
+
+nc_default_outdir_2(Config) ->
+ R = filename:join(proplists:get_value(data_dir, Config), "m"),
+ W = proplists:get_value(priv_dir, Config),
+ file:set_cwd(W),
+ Obj = "m" ++ code:objfile_extension(),
+ _ = file:delete(Obj),
+ false = filelib:is_file(Obj),
+ Result = c:nc(R),
+ {ok, m} = Result,
+ true = filelib:is_file(Obj).
+
ls(Config) when is_list(Config) ->
Directory = proplists:get_value(data_dir, Config),
ok = c:ls(Directory),
diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl
index 71d6820c47..915f478dfa 100644
--- a/lib/stdlib/test/epp_SUITE.erl
+++ b/lib/stdlib/test/epp_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
otp_8130/1, overload_mac/1, otp_8388/1, otp_8470/1,
otp_8562/1, otp_8665/1, otp_8911/1, otp_10302/1, otp_10820/1,
otp_11728/1, encoding/1, extends/1, function_macro/1,
- test_error/1, test_warning/1]).
+ test_error/1, test_warning/1, otp_14285/1]).
-export([epp_parse_erl_form/2]).
@@ -68,7 +68,8 @@ all() ->
not_circular, skip_header, otp_6277, otp_7702, otp_8130,
overload_mac, otp_8388, otp_8470, otp_8562,
otp_8665, otp_8911, otp_10302, otp_10820, otp_11728,
- encoding, extends, function_macro, test_error, test_warning].
+ encoding, extends, function_macro, test_error, test_warning,
+ otp_14285].
groups() ->
[{upcase_mac, [], [upcase_mac_1, upcase_mac_2]},
@@ -677,7 +678,7 @@ otp_8130(Config) when is_list(Config) ->
{otp_8130_c6,
<<"-define(M3(), A).\n"
"t() -> A = 1, ?3.14159}.\n">>,
- {errors,[{{2,16},epp,{call,"?3.14159"}}],[]}},
+ {errors,[{{2,16},epp,{call,[$?,"3.14159"]}}],[]}},
{otp_8130_c7,
<<"\nt() -> ?A.\n">>,
@@ -1384,6 +1385,26 @@ do_otp_10820(File, C, PC) ->
true = test_server:stop_node(Node),
ok.
+%% OTP_14285: Unicode atoms.
+otp_14285(Config) when is_list(Config) ->
+ %% This is just a sample of errors.
+ Cs = [{otp_8562,
+ <<"-export([f/0]).
+ -define('a\x{400}b', 'a\x{400}d').
+ f() ->
+ ?'a\x{400}b'.
+ g() ->
+ ?\"a\x{400}b\".
+ h() ->
+ ?'a\x{400}no'().
+ "/utf8>>,
+ {errors,[{6,epp,{call,[63,[91,["97",44,"1024",44,"98"],93]]}},
+ {8,epp,{undefined,'a\x{400}no',0}}],
+ []}}
+ ],
+ [] = compile(Config, Cs),
+ ok.
+
%% OTP-11728. Bugfix circular macro.
otp_11728(Config) when is_list(Config) ->
Dir = proplists:get_value(priv_dir, Config),
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index 02524679fa..cb1cceb8db 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -66,7 +66,7 @@
otp_11851/1,otp_11879/1,otp_13230/1,
record_errors/1, otp_11879_cont/1,
non_latin1_module/1, otp_14323/1,
- get_stacktrace/1]).
+ get_stacktrace/1, otp_14285/1, otp_14378/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -87,7 +87,7 @@ all() ->
maps, maps_type, maps_parallel_match,
otp_11851, otp_11879, otp_13230,
record_errors, otp_11879_cont, non_latin1_module, otp_14323,
- get_stacktrace].
+ get_stacktrace, otp_14285, otp_14378].
groups() ->
[{unused_vars_warn, [],
@@ -2054,12 +2054,10 @@ otp_5362(Config) when is_list(Config) ->
spawn(A).
">>,
{[nowarn_unused_function]},
- {error,[{3,erl_lint,disallowed_nowarn_bif_clash},
- {4,erl_lint,disallowed_nowarn_bif_clash},
- {4,erl_lint,{bad_nowarn_bif_clash,{spawn,2}}}],
- [{5,erl_lint,{bad_nowarn_deprecated_function,{3,now,-1}}},
- {5,erl_lint,{bad_nowarn_deprecated_function,{erlang,now,-1}}},
- {5,erl_lint,{bad_nowarn_deprecated_function,{{a,b,c},now,-1}}}]}
+ {errors,[{3,erl_lint,disallowed_nowarn_bif_clash},
+ {4,erl_lint,disallowed_nowarn_bif_clash},
+ {4,erl_lint,{bad_nowarn_bif_clash,{spawn,2}}}],
+ []}
},
{otp_5362_8,
@@ -3922,10 +3920,63 @@ otp_11879_cont(Config) ->
%% OTP-14285: We currently don't support non-latin1 module names.
-non_latin1_module(_Config) ->
+non_latin1_module(Config) ->
do_non_latin1_module('юникод'),
do_non_latin1_module(list_to_atom([256,$a,$b,$c])),
do_non_latin1_module(list_to_atom([$a,$b,256,$c])),
+
+ "module names with non-latin1 characters are not supported" =
+ format_error(non_latin1_module_unsupported),
+ BadCallback =
+ {bad_callback,{'кирилли́ческий атом','кирилли́ческий атом',0}},
+ "explicit module not allowed for callback "
+ "'кирилли́ческий атом':'кирилли́ческий атом'/0" =
+ format_error(BadCallback),
+ UndefBehav = {undefined_behaviour,'кирилли́ческий атом'},
+ "behaviour 'кирилли́ческий атом' undefined" =
+ format_error(UndefBehav),
+ Ts = [{non_latin1_module,
+ <<"
+ %% Report uses of module names with non-Latin-1 characters.
+
+ -import('кирилли́ческий атом', []).
+ -behaviour('кирилли́ческий атом').
+ -behavior('кирилли́ческий атом').
+
+ -callback 'кирилли́ческий атом':'кирилли́ческий атом'() -> a.
+
+ %% erl_lint:gexpr/3 is not extended to check module name here:
+ t1() when 'кирилли́ческий атом':'кирилли́ческий атом'(1) ->
+ b.
+
+ t2() ->
+ 'кирилли́ческий атом':'кирилли́ческий атом'().
+
+ -spec 'кирилли́ческий атом':'кирилли́ческий атом'() -> atom().
+
+ -spec 'кирилли́ческий атом'(integer()) ->
+ 'кирилли́ческий атом':'кирилли́ческий атом'().
+
+ 'кирилли́ческий атом'(1) ->
+ 'кирилли́ческий атом':f(),
+ F = f,
+ 'кирилли́ческий атом':F()."/utf8>>,
+ [],
+ {error,
+ [{4,erl_lint,non_latin1_module_unsupported},
+ {5,erl_lint,non_latin1_module_unsupported},
+ {6,erl_lint,non_latin1_module_unsupported},
+ {8,erl_lint,non_latin1_module_unsupported},
+ {8,erl_lint,BadCallback},
+ {11,erl_lint,illegal_guard_expr},
+ {15,erl_lint,non_latin1_module_unsupported},
+ {17,erl_lint,non_latin1_module_unsupported},
+ {20,erl_lint,non_latin1_module_unsupported},
+ {23,erl_lint,non_latin1_module_unsupported},
+ {25,erl_lint,non_latin1_module_unsupported}],
+ [{5,erl_lint,UndefBehav},
+ {6,erl_lint,UndefBehav}]}}],
+ run(Config, Ts),
ok.
do_non_latin1_module(Mod) ->
@@ -3938,6 +3989,22 @@ do_non_latin1_module(Mod) ->
ok.
+otp_14378(Config) ->
+ Ts = [
+ {otp_14378_1,
+ <<"-export([t/0]).
+ -compile({nowarn_deprecated_function,{erlang,now,1}}).
+ t() ->
+ erlang:now().">>,
+ [],
+ {warnings,[{4,erl_lint,
+ {deprecated,{erlang,now,0},
+ "Deprecated BIF. See the \"Time and Time Correction"
+ " in Erlang\" chapter of the ERTS User's Guide"
+ " for more information."}}]}}],
+ [] = run(Config, Ts),
+ ok.
+
%% OTP-14323: Check the dialyzer attribute.
otp_14323(Config) ->
Ts = [
@@ -3978,7 +4045,11 @@ otp_14323(Config) ->
{13,erl_lint,{undefined_function,{a,1}}},
{14,erl_lint,{bad_dialyzer_attribute,
{nowarn_function,{a,-1}}}}],
- []}}],
+ []}},
+ {otp_14323_2,
+ <<"-type t(_) :: atom().">>,
+ [],
+ {errors,[{1,erl_parse,"bad type variable"}],[]}}],
[] = run(Config, Ts),
ok.
@@ -4038,6 +4109,53 @@ get_stacktrace(Config) ->
run(Config, Ts),
ok.
+%% Unicode atoms.
+otp_14285(Config) ->
+ %% A small sample of all the errors and warnings in module erl_lint.
+ E1 = {redefine_function,{'кирилли́ческий атом',0}},
+ E2 = {attribute,'кирилли́ческий атом'},
+ E3 = {undefined_record,'кирилли́ческий атом'},
+ E4 = {undefined_bittype,'кирилли́ческий атом'},
+ "function 'кирилли́ческий атом'/0 already defined" = format_error(E1),
+ "attribute 'кирилли́ческий атом' after function definitions" =
+ format_error(E2),
+ "record 'кирилли́ческий атом' undefined" = format_error(E3),
+ "bit type 'кирилли́ческий атом' undefined" = format_error(E4),
+ Ts = [{otp_14285_1,
+ <<"'кирилли́ческий атом'() -> a.
+ 'кирилли́ческий атом'() -> a.
+ "/utf8>>,
+ [],
+ {errors,
+ [{2,erl_lint,E1}],
+ []}},
+ {otp_14285_2,
+ <<"'кирилли́ческий атом'() -> a.
+ -'кирилли́ческий атом'(a).
+ "/utf8>>,
+ [],
+ {errors,
+ [{2,erl_lint,E2}],
+ []}},
+ {otp_14285_3,
+ <<"'кирилли́ческий атом'() -> #'кирилли́ческий атом'{}.
+ "/utf8>>,
+ [],
+ {errors,
+ [{1,erl_lint,E3}],
+ []}},
+ {otp_14285_4,
+ <<"t() -> <<34/'кирилли́ческий атом'>>.
+ "/utf8>>,
+ [],
+ {errors,
+ [{1,erl_lint,E4}],
+ []}}],
+ run(Config, Ts),
+ ok.
+
+format_error(E) ->
+ lists:flatten(erl_lint:format_error(E)).
run(Config, Tests) ->
F = fun({N,P,Ws,E}, BadL) ->
diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl
index 7e3c71715e..2e9dc4d4fb 100644
--- a/lib/stdlib/test/gen_server_SUITE.erl
+++ b/lib/stdlib/test/gen_server_SUITE.erl
@@ -404,7 +404,7 @@ crash(Config) when is_list(Config) ->
end,
receive
{error_report,_,{Pid4,crash_report,[List4|_]}} ->
- {exit,crashed,_} = proplists:get_value(error_info, List4),
+ {exit,crashed,[{?MODULE, handle_call, 3, _}|_]} = proplists:get_value(error_info, List4),
Pid4 = proplists:get_value(pid, List4);
Other4 ->
io:format("Unexpected: ~p", [Other4]),
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
index ef3f0be5d7..e2c73371cd 100644
--- a/lib/stdlib/test/io_SUITE.erl
+++ b/lib/stdlib/test/io_SUITE.erl
@@ -31,7 +31,7 @@
otp_10836/1, io_lib_width_too_small/1,
io_with_huge_message_queue/1, format_string/1,
maps/1, coverage/1, otp_14178_unicode_atoms/1, otp_14175/1,
- otp_14285/1]).
+ otp_14285/1, limit_term/1]).
-export([pretty/2]).
@@ -63,7 +63,7 @@ all() ->
io_lib_print_binary_depth_one, otp_10302, otp_10755, otp_10836,
io_lib_width_too_small, io_with_huge_message_queue,
format_string, maps, coverage, otp_14178_unicode_atoms, otp_14175,
- otp_14285].
+ otp_14285, limit_term].
%% Error cases for output.
error_1(Config) when is_list(Config) ->
@@ -2373,3 +2373,58 @@ otp_14285(_Config) ->
latin1_fmt(Fmt, Args) ->
L = fmt(Fmt, Args),
true = lists:all(fun is_latin1/1, L).
+
+limit_term(_Config) ->
+ {_, 2} = limt([a,b,c], 2),
+ {_, 2} = limt([a,b,c], 3),
+ {_, 2} = limt([a,b|c], 2),
+ {_, 2} = limt([a,b|c], 3),
+ {_, 2} = limt({a,b,c,[d,e]}, 2),
+ {_, 2} = limt({a,b,c,[d,e]}, 3),
+ {_, 2} = limt({a,b,c,[d,e]}, 4),
+ {_, 1} = limt(<<"foo">>, 18),
+ ok = blimt(<<"123456789012345678901234567890">>),
+ {_, 1} = limt(<<7:3>>, 2),
+ {_, 1} = limt(<<7:21>>, 2),
+ {_, 1} = limt([], 2),
+ {_, 1} = limt({}, 2),
+ {_, 1} = limt(#{}, 2),
+ {_, 1} = limt(#{[] => {}}, 2),
+ {_, 1} = limt(#{[] => {}}, 3),
+ T = #{[] => {},[a] => [b]},
+ {_, 1} = limt(T, 2),
+ {_, 1} = limt(T, 3),
+ {_, 1} = limt(T, 4),
+ ok.
+
+blimt(Binary) ->
+ blimt(Binary, byte_size(Binary)).
+
+blimt(_B, 1) -> ok;
+blimt(B, D) ->
+ {_, 1} = limt(B, D),
+ blimt(B, D - 1).
+
+limt(Term, Depth) when is_integer(Depth) ->
+ T1 = io_lib:limit_term(Term, Depth),
+ S = form(Term, Depth),
+ S1 = form(T1, Depth),
+ OK1 = S1 =:= S,
+
+ T2 = io_lib:limit_term(Term, Depth+1),
+ S2 = form(T2, Depth),
+ OK2 = S2 =:= S,
+
+ T3 = io_lib:limit_term(Term, Depth-1),
+ S3 = form(T3, Depth),
+ OK3 = S3 =/= S,
+
+ R = case {OK1, OK2, OK3} of
+ {true, true, true} -> 2;
+ {true, true, false} -> 1;
+ _ -> 0
+ end,
+ {{S, S1, S2}, R}.
+
+form(Term, Depth) ->
+ lists:flatten(io_lib:format("~W", [Term, Depth])).
diff --git a/lib/stdlib/test/ms_transform_SUITE.erl b/lib/stdlib/test/ms_transform_SUITE.erl
index f35013b1b2..d1e6faf863 100644
--- a/lib/stdlib/test/ms_transform_SUITE.erl
+++ b/lib/stdlib/test/ms_transform_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@
-export([warnings/1]).
-export([no_warnings/1]).
-export([eep37/1]).
+-export([otp_14454/1]).
init_per_testcase(_Func, Config) ->
Config.
@@ -59,7 +60,7 @@ all() ->
record_index, multipass, bitsyntax, record_defaults,
andalso_orelse, float_1_function, action_function,
warnings, no_warnings, top_match, old_guards, autoimported,
- semicolon, eep37].
+ semicolon, eep37, otp_14454].
groups() ->
[].
@@ -771,6 +772,16 @@ eep37(Config) when is_list(Config) ->
"F()">>).
+otp_14454(Config) when is_list(Config) ->
+ setup(Config),
+ [{'$1',[],[{'band','$1',136}]}] =
+ compile_and_run(
+ <<"ets:fun2ms(fun(A) -> A band ( -(-17) bsl 3) end)">>),
+ [{'$1',[],[{'band','$1',136}]}] =
+ compile_and_run(
+ <<"ets:fun2ms(fun(A) -> A band ( erlang:'bsl'(-(-17), 3)) end)">>),
+ ok.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Helpers
diff --git a/lib/stdlib/test/proc_lib_SUITE.erl b/lib/stdlib/test/proc_lib_SUITE.erl
index a53e99afc9..c4fafe82a4 100644
--- a/lib/stdlib/test/proc_lib_SUITE.erl
+++ b/lib/stdlib/test/proc_lib_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -27,8 +27,8 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
crash/1, stacktrace/1, sync_start_nolink/1, sync_start_link/1,
- spawn_opt/1, sp1/0, sp2/0, sp3/1, sp4/2, sp5/1,
- hibernate/1, stop/1, t_format/1]).
+ spawn_opt/1, sp1/0, sp2/0, sp3/1, sp4/2, sp5/1, '\x{447}'/0,
+ hibernate/1, stop/1, t_format/1, t_format_arbitrary/1]).
-export([ otp_6345/1, init_dont_hang/1]).
-export([hib_loop/1, awaken/1]).
@@ -51,7 +51,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[crash, stacktrace, {group, sync_start}, spawn_opt, hibernate,
- {group, tickets}, stop, t_format].
+ {group, tickets}, stop, t_format, t_format_arbitrary].
groups() ->
[{tickets, [], [otp_6345, init_dont_hang]},
@@ -78,6 +78,14 @@ end_per_group(_GroupName, Config) ->
%% synchronous, and we want to test that the crash report is ok.
%%-----------------------------------------------------------------
crash(Config) when is_list(Config) ->
+ ok = application:unset_env(kernel, error_logger_format_depth),
+ crash_1(Config),
+ ok = application:set_env(kernel, error_logger_format_depth, 30),
+ crash_1(Config),
+ ok = application:unset_env(kernel, error_logger_format_depth),
+ ok.
+
+crash_1(_Config) ->
error_logger:add_report_handler(?MODULE, self()),
%% Make sure that we don't get a crash report if a process
@@ -139,6 +147,14 @@ crash(Config) when is_list(Config) ->
{error_info,{exit,abnormal,{stacktrace}}}],
analyse_crash(Pid5, Exp5, []),
+ %% Unicode atom
+ Pid6 = proc_lib:spawn(?MODULE, '\x{447}', []),
+ Pid6 ! die,
+ Exp6 = [{initial_call,{?MODULE,'\x{447}',[]}},
+ {ancestors,[self()]},
+ {error_info,{exit,die,{stacktrace}}}],
+ analyse_crash(Pid6, Exp6, []),
+
error_logger:delete_report_handler(?MODULE),
ok.
@@ -304,6 +320,12 @@ sp4(Parent, Tester) ->
end,
proc_lib:init_ack(Parent, self()).
+'\x{447}'() ->
+ receive
+ die -> exit(die);
+ _ -> sp1()
+ end.
+
hibernate(Config) when is_list(Config) ->
Ref = make_ref(),
Self = self(),
@@ -548,9 +570,32 @@ t_format() ->
ok.
+t_format_arbitrary(_Config) ->
+ error_logger:tty(false),
+ try
+ t_format_arbitrary()
+ after
+ error_logger:tty(true)
+ end,
+ ok.
+
+t_format_arbitrary() ->
+ A = list_to_atom([1024]),
+ do_test_format([fake_report, A], unlimited),
+ do_test_format([fake_report, A], 20),
+
+ do_test_format([fake_report, foo], unlimited),
+ do_test_format([fake_report, foo], 20),
+ do_test_format([fake_report, []], unlimited),
+ do_test_format([fake_report, []], 20).
+
do_test_format(Report, Depth) ->
- io:format("*** Depth = ~p", [Depth]),
- S0 = proc_lib:format(Report, latin1, Depth),
+ do_test_format(Report, latin1, Depth),
+ do_test_format(Report, unicode, Depth).
+
+do_test_format(Report, Encoding, Depth) ->
+ io:format("*** Depth = ~p, Encoding = ~p", [Depth, Encoding]),
+ S0 = proc_lib:format(Report, Encoding, Depth),
S = lists:flatten(S0),
io:put_chars(S),
length(S).
@@ -570,7 +615,7 @@ init(Tester) ->
{ok, Tester}.
handle_event({error_report, _GL, {Pid, crash_report, Report}}, Tester) ->
- io:format("~s\n", [proc_lib:format(Report)]),
+ io:format("~ts\n", [proc_lib:format(Report)]),
Tester ! {crash_report, Pid, Report},
{ok, Tester};
handle_event(_Event, State) ->
diff --git a/lib/stdlib/test/shell_SUITE.erl b/lib/stdlib/test/shell_SUITE.erl
index 99411bc8fd..217e8cc252 100644
--- a/lib/stdlib/test/shell_SUITE.erl
+++ b/lib/stdlib/test/shell_SUITE.erl
@@ -31,7 +31,7 @@
progex_lc/1, progex_funs/1,
otp_5990/1, otp_6166/1, otp_6554/1,
otp_7184/1, otp_7232/1, otp_8393/1, otp_10302/1, otp_13719/1,
- otp_14285/1, otp_14296/1]).
+ otp_14285/1, otp_14296/1, typed_records/1]).
-export([ start_restricted_from_shell/1,
start_restricted_on_command_line/1,restricted_local/1]).
@@ -74,10 +74,10 @@ suite() ->
{timetrap,{minutes,10}}].
all() ->
- [forget, records, known_bugs, otp_5226, otp_5327,
+ [forget, known_bugs, otp_5226, otp_5327,
otp_5435, otp_5195, otp_5915, otp_5916, {group, bits},
{group, refman}, {group, progex}, {group, tickets},
- {group, restricted}].
+ {group, restricted}, {group, records}].
groups() ->
[{restricted, [],
@@ -86,6 +86,8 @@ groups() ->
{bits, [],
[bs_match_misc_SUITE, bs_match_tail_SUITE,
bs_match_bin_SUITE, bs_construct_SUITE]},
+ {records, [],
+ [records, typed_records]},
{refman, [], [refman_bit_syntax]},
{progex, [],
[progex_bit_syntax, progex_records, progex_lc,
@@ -376,6 +378,9 @@ records(Config) when is_list(Config) ->
[[state]] = scan(RR4),
Test = filename:join(proplists:get_value(priv_dir, Config), "test.erl"),
+ BeamDir = filename:join(proplists:get_value(priv_dir, Config), "beam"),
+ BeamFile = filename:join(BeamDir, "test"),
+ ok = file:make_dir(BeamDir),
Contents = <<"-module(test).
-record(state, {bin :: binary(),
reply = no,
@@ -387,8 +392,10 @@ records(Config) when is_list(Config) ->
-ifdef(test2).
-record(test2, {g}).
- -endif.">>,
+ -endif.
+ ">>,
ok = file:write_file(Test, Contents),
+ {ok, test} = compile:file(Test, [{outdir, BeamDir}]),
RR5 = "rr(\"" ++ Test ++ "\", '_', {d,test1}), rl([test1,test2]).",
A1 = erl_anno:new(1),
@@ -404,7 +411,11 @@ records(Config) when is_list(Config) ->
Dir = filename:join(proplists:get_value(priv_dir, Config), "*.erl"),
RR8 = "rp(rr(\"" ++ Dir ++ "\")).",
[_,ok] = scan(RR8),
+
+ {module, test} = code:load_abs(BeamFile),
+ [[state]] = scan(<<"rr(test).">>),
file:delete(Test),
+ file:delete(BeamFile++".beam"),
RR1000 = "begin rr(" ++ MS ++ ") end.",
[_] = scan(RR1000),
@@ -477,6 +488,48 @@ records(Config) when is_list(Config) ->
ok.
+%% Test of typed record support.
+typed_records(Config) when is_list(Config) ->
+ Test = filename:join(proplists:get_value(priv_dir, Config), "test.hrl"),
+ Contents = <<"-module(test).
+ -record(r0,{f :: any()}).
+ -record(r1,{f1 :: #r1{} | undefined, f2 :: #r0{} | atom()}).
+ -record(r2,{f :: #r2{} | undefined}).
+ ">>,
+ ok = file:write_file(Test, Contents),
+
+ RR1 = "rr(\"" ++ Test ++ "\"),
+ #r1{} = (#r1{f1=#r1{f1=undefined, f2=x}, f2 = #r0{}})#r1.f1,
+ ok.",
+ RR2 = "rr(\"" ++ Test ++ "\"),
+ #r0{} = (#r1{f1=#r1{f1=undefined, f2=x}, f2 = #r0{}})#r1.f2,
+ ok. ",
+ RR3 = "rr(\"" ++ Test ++ "\"),
+ #r1{f2=#r0{}} = (#r1{f1=#r1{f1=undefined, f2=#r0{}}, f2 = x})#r1.f1,
+ ok.",
+ RR4 = "rr(\"" ++ Test ++ "\"),
+ (#r1{f2 = #r0{}})#r1{f2 = x},
+ ok. ",
+ RR5 = "rr(\"" ++ Test ++ "\"),
+ (#r1{f2 = #r0{}})#r1{f1 = #r1{}},
+ ok. ",
+ RR6 = "rr(\"" ++ Test ++ "\"),
+ (#r2{f=#r2{f=undefined}})#r2.f,
+ ok.",
+ RR7 = "rr(\"" ++ Test ++ "\"),
+ #r2{} = (#r2{f=#r2{f=undefined}})#r2.f,
+ ok.",
+ [ok] = scan(RR1),
+ [ok] = scan(RR2),
+ [ok] = scan(RR3),
+ [ok] = scan(RR4),
+ [ok] = scan(RR5),
+ [ok] = scan(RR6),
+ [ok] = scan(RR7),
+
+ file:delete(Test),
+ ok.
+
%% Known bugs.
known_bugs(Config) when is_list(Config) ->
%% erl_eval:merge_bindings/2 cannot handle _removal_ of bindings.
@@ -2671,7 +2724,7 @@ prompt_err(B) ->
S = string:strip(S2, both, $"),
string:strip(S, right, $.).
-%% OTP-10302. Unicode.
+%% OTP-10302. Unicode. Also OTP-14285, Unicode atoms.
otp_10302(Config) when is_list(Config) ->
{ok,Node} = start_node(shell_suite_helper_2,
"-pa "++proplists:get_value(priv_dir,Config)++
@@ -2809,6 +2862,22 @@ otp_10302(Config) when is_list(Config) ->
" erl_eval:'-inside-an-interpreted-fun-'(65,\"\x{441}\")"
" .\n" = t({Node,Test13}),
+ %% Unicode atoms.
+ Test14 = <<"'\\x{447}\\x{435}'().">>,
+ "** exception error: undefined shell command '\\x{447}\\x{435}'/0.\n" =
+ t(Test14),
+ Test15 = <<"io:setopts([{encoding,utf8}]).
+ '\\x{447}\\x{435}'().">>,
+ "ok.\n** exception error: undefined shell command '\x{447}\x{435}'/0.\n" =
+ t({Node,Test15}),
+ Test16 = <<"shell_SUITE:'\\x{447}\\x{435}'().">>,
+ "** exception error: undefined function "
+ "shell_SUITE:'\\x{447}\\x{435}'/0.\n" = t(Test16),
+ Test17 = <<"io:setopts([{encoding,utf8}]).
+ shell_SUITE:'\\x{447}\\x{435}'().">>,
+ "ok.\n** exception error: undefined function "
+ "shell_SUITE:'\x{447}\x{435}'/0.\n" =
+ t({Node,Test17}),
test_server:stop_node(Node),
ok.
diff --git a/lib/stdlib/test/string_SUITE.erl b/lib/stdlib/test/string_SUITE.erl
index 4320b735ac..90f980c0e5 100644
--- a/lib/stdlib/test/string_SUITE.erl
+++ b/lib/stdlib/test/string_SUITE.erl
@@ -582,6 +582,8 @@ cd_gc(_) ->
[$e,778] = string:next_codepoint([$e,778]),
[$e|<<204,138>>] = string:next_codepoint(<<$e,778/utf8>>),
[778|_] = string:next_codepoint(tl(string:next_codepoint(<<$e,778/utf8>>))),
+ [0|<<128,1>>] = string:next_codepoint(<<0,128,1>>),
+ {error,<<128,1>>} = string:next_codepoint(<<128,1>>),
[] = string:next_grapheme(""),
[] = string:next_grapheme(<<>>),
@@ -589,6 +591,8 @@ cd_gc(_) ->
"abcd" = string:next_grapheme("abcd"),
[[$e,778]] = string:next_grapheme([$e,778]),
[[$e,778]] = string:next_grapheme(<<$e,778/utf8>>),
+ [0|<<128,1>>] = string:next_grapheme(<<0,128,1>>),
+ {error,<<128,1>>} = string:next_grapheme(<<128,1>>),
ok.
diff --git a/lib/stdlib/test/tar_SUITE.erl b/lib/stdlib/test/tar_SUITE.erl
index e9ab12e061..4061008812 100644
--- a/lib/stdlib/test/tar_SUITE.erl
+++ b/lib/stdlib/test/tar_SUITE.erl
@@ -27,7 +27,8 @@
extract_from_binary_compressed/1, extract_filtered/1,
extract_from_open_file/1, symlinks/1, open_add_close/1, cooked_compressed/1,
memory/1,unicode/1,read_other_implementations/1,
- sparse/1, init/1, leading_slash/1, dotdot/1]).
+ sparse/1, init/1, leading_slash/1, dotdot/1,
+ roundtrip_metadata/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
@@ -41,7 +42,7 @@ all() ->
extract_filtered,
symlinks, open_add_close, cooked_compressed, memory, unicode,
read_other_implementations,
- sparse,init,leading_slash,dotdot].
+ sparse,init,leading_slash,dotdot,roundtrip_metadata].
groups() ->
[].
@@ -953,6 +954,42 @@ dotdot(Config) ->
ok.
+roundtrip_metadata(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Dir = filename:join(PrivDir, ?FUNCTION_NAME),
+ ok = file:make_dir(Dir),
+
+ do_roundtrip_metadata(Dir, "name-does-not-matter"),
+ ok.
+
+do_roundtrip_metadata(Dir, File) ->
+ Tar = filename:join(Dir, atom_to_list(?FUNCTION_NAME)++".tar"),
+ BeamFile = code:which(compile),
+ {ok,Fd} = erl_tar:open(Tar, [write]),
+ ok = erl_tar:add(Fd, BeamFile, File, []),
+ ok = erl_tar:close(Fd),
+
+ ok = erl_tar:extract(Tar, [{cwd,Dir}]),
+
+ %% Make sure that size and modification times are the same
+ %% on all platforms.
+ {ok,OrigInfo} = file:read_file_info(BeamFile),
+ ExtractedFile = filename:join(Dir, File),
+ {ok,ExtractedInfo} = file:read_file_info(ExtractedFile),
+ #file_info{size=Size,mtime=Mtime,type=regular} = OrigInfo,
+ #file_info{size=Size,mtime=Mtime,type=regular} = ExtractedInfo,
+
+ %% On Unix platforms more fields are expected to be the same.
+ case os:type() of
+ {unix,_} ->
+ #file_info{access=Access,mode=Mode} = OrigInfo,
+ #file_info{access=Access,mode=Mode} = ExtractedInfo,
+ ok;
+ _ ->
+ ok
+ end.
+
+
%% Delete the given list of files.
delete_files([]) -> ok;
delete_files([Item|Rest]) ->
diff --git a/lib/stdlib/test/unicode_SUITE.erl b/lib/stdlib/test/unicode_SUITE.erl
index 3d97ab93f1..e01ba3fbb0 100644
--- a/lib/stdlib/test/unicode_SUITE.erl
+++ b/lib/stdlib/test/unicode_SUITE.erl
@@ -998,6 +998,30 @@ normalize(_) ->
true = unicode:characters_to_nfkc_list("ホンダ") =:= unicode:characters_to_nfkc_list("ホンダ"),
true = unicode:characters_to_nfkd_list("32") =:= unicode:characters_to_nfkd_list("32"),
+
+ {error, [0], <<128>>} = unicode:characters_to_nfc_list(<<0, 128>>),
+ {error, [0], <<128>>} = unicode:characters_to_nfkc_list(<<0, 128>>),
+ {error, [0], <<128>>} = unicode:characters_to_nfd_list(<<0, 128>>),
+ {error, [0], <<128>>} = unicode:characters_to_nfkd_list(<<0, 128>>),
+
+ {error, <<0>>, <<128>>} = unicode:characters_to_nfc_binary(<<0, 128>>),
+ {error, <<0>>, <<128>>} = unicode:characters_to_nfkc_binary(<<0, 128>>),
+ {error, <<0>>, <<128>>} = unicode:characters_to_nfd_binary(<<0, 128>>),
+ {error, <<0>>, <<128>>} = unicode:characters_to_nfkd_binary(<<0, 128>>),
+
+ LargeBin = binary:copy(<<"abcde">>, 50),
+ LargeList = binary_to_list(LargeBin),
+
+ {error, LargeList, <<128>>} = unicode:characters_to_nfc_list(<<LargeBin/binary, 128>>),
+ {error, LargeList, <<128>>} = unicode:characters_to_nfkc_list(<<LargeBin/binary, 128>>),
+ {error, LargeList, <<128>>} = unicode:characters_to_nfd_list(<<LargeBin/binary, 128>>),
+ {error, LargeList, <<128>>} = unicode:characters_to_nfkd_list(<<LargeBin/binary, 128>>),
+
+ {error, LargeBin, <<128>>} = unicode:characters_to_nfc_binary(<<LargeBin/binary, 128>>),
+ {error, LargeBin, <<128>>} = unicode:characters_to_nfkc_binary(<<LargeBin/binary, 128>>),
+ {error, LargeBin, <<128>>} = unicode:characters_to_nfd_binary(<<LargeBin/binary, 128>>),
+ {error, LargeBin, <<128>>} = unicode:characters_to_nfkd_binary(<<LargeBin/binary, 128>>),
+
ok.
diff --git a/lib/stdlib/test/unicode_util_SUITE.erl b/lib/stdlib/test/unicode_util_SUITE.erl
index e9b3d7f98d..03c24c7027 100644
--- a/lib/stdlib/test/unicode_util_SUITE.erl
+++ b/lib/stdlib/test/unicode_util_SUITE.erl
@@ -97,6 +97,8 @@ cp(_) ->
"hejsan" = fetch(<<"hejsan">>, Get),
"hejsan" = fetch(["hej",<<"san">>], Get),
"hejsan" = fetch(["hej"|<<"san">>], Get),
+ {error, <<128>>} = Get(<<128>>),
+ {error, [<<128>>, 0]} = Get([<<128>>, 0]),
ok.
gc(Config) ->
@@ -106,6 +108,8 @@ gc(Config) ->
"hejsan" = fetch(<<"hejsan">>, Get),
"hejsan" = fetch(["hej",<<"san">>], Get),
"hejsan" = fetch(["hej"|<<"san">>], Get),
+ {error, <<128>>} = Get(<<128>>),
+ {error, [<<128>>, 0]} = Get([<<128>>, 0]),
0 = fold(fun verify_gc/3, 0, DataDir ++ "/GraphemeBreakTest.txt"),
ok.
diff --git a/lib/stdlib/uc_spec/gen_unicode_mod.escript b/lib/stdlib/uc_spec/gen_unicode_mod.escript
index c8b815e435..fefd7d3b70 100755
--- a/lib/stdlib/uc_spec/gen_unicode_mod.escript
+++ b/lib/stdlib/uc_spec/gen_unicode_mod.escript
@@ -170,7 +170,7 @@ gen_header(Fd) ->
io:put_chars(Fd, "-export([spec_version/0, lookup/1, get_case/1]).\n"),
io:put_chars(Fd, "-inline([class/1]).\n"),
io:put_chars(Fd, "-compile(nowarn_unused_vars).\n"),
- io:put_chars(Fd, "-dialyzer({no_improper_lists, cp/1}).\n"),
+ io:put_chars(Fd, "-dialyzer({no_improper_lists, [cp/1, gc_prepend/2, gc_e_cont/2]}).\n"),
io:put_chars(Fd, "-type gc() :: char()|[char()].\n\n\n"),
ok.
@@ -237,39 +237,43 @@ gen_static(Fd) ->
gen_norm(Fd) ->
io:put_chars(Fd,
- "-spec nfd(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()).\n"
+ "-spec nfd(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()) | {error, unicode:chardata()}.\n"
"nfd(Str0) ->\n"
" case gc(Str0) of\n"
" [GC|R] when GC < 127 -> [GC|R];\n"
" [GC|Str] -> [decompose(GC)|Str];\n"
- " [] -> []\n end.\n\n"
+ " [] -> [];\n"
+ " {error,_}=Error -> Error\n end.\n\n"
),
io:put_chars(Fd,
- "-spec nfkd(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()).\n"
+ "-spec nfkd(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()) | {error, unicode:chardata()}.\n"
"nfkd(Str0) ->\n"
" case gc(Str0) of\n"
" [GC|R] when GC < 127 -> [GC|R];\n"
" [GC|Str] -> [decompose_compat(GC)|Str];\n"
- " [] -> []\n end.\n\n"
+ " [] -> [];\n"
+ " {error,_}=Error -> Error\n end.\n\n"
),
io:put_chars(Fd,
- "-spec nfc(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()).\n"
+ "-spec nfc(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()) | {error, unicode:chardata()}.\n"
"nfc(Str0) ->\n"
" case gc(Str0) of\n"
" [GC|R] when GC < 255 -> [GC|R];\n"
" [GC|Str] -> [compose(decompose(GC))|Str];\n"
- " [] -> []\n end.\n\n"
+ " [] -> [];\n"
+ " {error,_}=Error -> Error\n end.\n\n"
),
io:put_chars(Fd,
- "-spec nfkc(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()).\n"
+ "-spec nfkc(unicode:chardata()) -> maybe_improper_list(gc(),unicode:chardata()) | {error, unicode:chardata()}.\n"
"nfkc(Str0) ->\n"
" case gc(Str0) of\n"
" [GC|R] when GC < 127 -> [GC|R];\n"
" [GC|Str] -> [compose_compat_0(decompose_compat(GC))|Str];\n"
- " [] -> []\n end.\n\n"
+ " [] -> [];\n"
+ " {error,_}=Error -> Error\n end.\n\n"
),
io:put_chars(Fd,
@@ -448,18 +452,20 @@ gen_ws(Fd, Props) ->
gen_cp(Fd) ->
io:put_chars(Fd, "-spec cp(String::unicode:chardata()) ->"
- " maybe_improper_list().\n"),
+ " maybe_improper_list() | {error, unicode:chardata()}.\n"),
io:put_chars(Fd, "cp([C|_]=L) when is_integer(C) -> L;\n"),
io:put_chars(Fd, "cp([List]) -> cp(List);\n"),
io:put_chars(Fd, "cp([List|R]) ->\n"),
io:put_chars(Fd, " case cp(List) of\n"),
io:put_chars(Fd, " [] -> cp(R);\n"),
io:put_chars(Fd, " [CP] -> [CP|R];\n"),
- io:put_chars(Fd, " [C|R0] -> [C|[R0|R]]\n"),
+ io:put_chars(Fd, " [C|R0] -> [C|[R0|R]];\n"),
+ io:put_chars(Fd, " {error,Error} -> {error,[Error|R]}\n"),
io:put_chars(Fd, " end;\n"),
io:put_chars(Fd, "cp([]) -> [];\n"),
io:put_chars(Fd, "cp(<<C/utf8, R/binary>>) -> [C|R];\n"),
- io:put_chars(Fd, "cp(<<>>) -> [].\n\n"),
+ io:put_chars(Fd, "cp(<<>>) -> [];\n"),
+ io:put_chars(Fd, "cp(<<R/binary>>) -> {error,R}.\n\n"),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -468,7 +474,7 @@ gen_gc(Fd, GBP) ->
%% see http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules
io:put_chars(Fd,
"-spec gc(String::unicode:chardata()) ->"
- " maybe_improper_list().\n"),
+ " maybe_improper_list() | {error, unicode:chardata()}.\n"),
io:put_chars(Fd,
"gc(Str) ->\n"
" gc_1(cp(Str)).\n\n"
@@ -521,7 +527,8 @@ gen_gc(Fd, GBP) ->
[GenEBG(CP) || CP <- merge_ranges(maps:get(e_base_gaz,GBP))],
io:put_chars(Fd, "gc_1([CP|R]) -> gc_extend(R, CP);\n"),
- io:put_chars(Fd, "gc_1([]) -> [].\n\n"),
+ io:put_chars(Fd, "gc_1([]) -> [];\n"),
+ io:put_chars(Fd, "gc_1({error,_}=Error) -> Error.\n\n"),
io:put_chars(Fd, "%% Handle Prepend\n"),
io:put_chars(Fd,
@@ -536,7 +543,8 @@ gen_gc(Fd, GBP) ->
" [GC|R1] -> [[CP0|GC]|R1]\n"
" end\n"
" end;\n"
- " [] -> [CP0]\n"
+ " [] -> [CP0];\n"
+ " {error,R} -> [CP0|R]\n"
" end.\n\n"),
IsCtrl = fun(Range) -> io:format(Fd, "is_control~s true;\n", [gen_single_clause(Range)]) end,
@@ -574,7 +582,10 @@ gen_gc(Fd, GBP) ->
" [_]=Acc -> Acc;\n"
" [_|_]=Acc -> [lists:reverse(Acc)];\n"
" Acc -> [Acc]\n"
- " end.\n\n"),
+ " end;\n"
+ "gc_extend({error,R}, T, Acc0) ->\n"
+ " gc_extend([], T, Acc0) ++ [R].\n\n"
+ ),
[ZWJ] = maps:get(zwj, GBP),
GenExtend = fun(R) when R =:= ZWJ -> io:format(Fd, "is_extend~s zwj;\n", [gen_single_clause(ZWJ)]);
(Range) -> io:format(Fd, "is_extend~s true;\n", [gen_single_clause(Range)])
@@ -604,6 +615,11 @@ gen_gc(Fd, GBP) ->
" case Acc of\n"
" [A] -> [A];\n"
" _ -> [lists:reverse(Acc)]\n"
+ " end;\n"
+ " {error,R} ->\n"
+ " case Acc of\n"
+ " [A] -> [A|R];\n"
+ " _ -> [lists:reverse(Acc)|R]\n"
" end\n"
" end.\n\n"),
@@ -660,6 +676,7 @@ gen_gc(Fd, GBP) ->
[GenHangulT_1(CP) || CP <- merge_ranges(maps:get(t,GBP))],
io:put_chars(Fd, " R1 -> gc_extend(R1, R0, Acc)\n end.\n\n"),
+ io:put_chars(Fd, "gc_h_lv_lvt({error,_}=Error, Acc) -> gc_extend(Error, [], Acc);\n"),
io:put_chars(Fd, "%% Handle Hangul LV\n"),
GenHangulLV = fun(Range) -> io:format(Fd, "gc_h_lv_lvt~s gc_h_V(R1,[CP|Acc]);\n",
[gen_clause2(Range)]) end,
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index f7bd21472c..8a83cdec1e 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1 +1 @@
-STDLIB_VSN = 3.3
+STDLIB_VSN = 3.4.1