[m-rev.] for review: bugfix for io.set_environment_var/4
Mark Brown
mark at mercurylang.org
Fri Apr 15 23:41:26 AEST 2016
Hi everyone,
This fixes a bug I hit recently, where some foreign code was not
picking up an environment variable I had set using
io.set_environment_var/4. The problem occurred after a GC freed the
memory held by the string passed to putenv(), which doesn't make a
copy on my platform (linux, glibc 2.21).
In its current form the change affects C backends for which MR_WIN32
is not defined. This is conservative: online documentation tells me it
is not needed on OSX or FreeBSD either, but I can't easily test that.
For review by anyone.
Cheers,
Mark
-------------- next part --------------
commit 29b4d39860e981b0d607f7edd006cf3a4df0742d
Author: Mark Brown <mark at mercurylang.org>
Date: Fri Apr 15 22:11:13 2016 +1000
Fix bug with io.set_environment_var/4 when using glibc.
library/io.m:
Prevent garbage collection of strings passed to putenv(), which
could otherwise leave dangling references in the environment.
tests/hard_coded/Mmakefile:
tests/hard_coded/putenv_gc_fail.{m,exp}:
Test case.
diff --git a/library/io.m b/library/io.m
index 28fab1a..2c32e73 100644
--- a/library/io.m
+++ b/library/io.m
@@ -10280,23 +10280,45 @@ command_line_argument(_, "") :-
end
").
+ % Glibc requires that we keep a copy of strings we pass to putenv(), so
+ % we store them here to prevent them being garbage collected. This map
+ % is *not* used to retrieve values from the environment: a map is used
+ % so that strings can be easily removed if a subsequent call sets a
+ % new value for the environment variable.
+ %
+:- mutable(putenv_strings, map(string, string), map.init, ground, [untrailed]).
+
io.setenv(Var, Value) :-
- impure io.putenv(Var ++ "=" ++ Value).
+ VarAndValue = Var ++ "=" ++ Value,
+ impure io.putenv(VarAndValue, Keep),
+ (
+ Keep = no
+ ;
+ Keep = yes,
+ semipure get_putenv_strings(Strings0),
+ map.set(Var, VarAndValue, Strings0, Strings),
+ impure set_putenv_strings(Strings)
+ ).
% io.putenv(VarString): If VarString is a string of the form "name=value",
% sets the environment variable name to the specified value. Fails if
% the operation does not work. This should only be called from io.setenv.
%
-:- impure pred io.putenv(string::in) is semidet.
+ % Returns 'yes' in the second argument if the caller is required to keep
+ % a copy of VarString (i.e., prevent it being garbage collected or freed).
+ %
+:- impure pred io.putenv(string::in, bool::out) is semidet.
:- pragma foreign_proc("C",
- io.putenv(VarAndValue::in),
+ io.putenv(VarAndValue::in, Keep::out),
[will_not_call_mercury, not_thread_safe, tabled_for_io,
does_not_affect_liveness, no_sharing],
"
#ifdef MR_WIN32
+ Keep = MR_NO;
SUCCESS_INDICATOR = (_wputenv(ML_utf8_to_wide(VarAndValue)) == 0);
#else
+ Keep = MR_YES;
SUCCESS_INDICATOR = (putenv(VarAndValue) == 0);
#endif
").
@@ -10314,7 +10336,7 @@ io.setenv(Var, Value) :-
").
:- pragma foreign_proc("C#",
- io.putenv(_VarAndValue::in),
+ io.putenv(_VarAndValue::in, _Keep::out),
[will_not_call_mercury, tabled_for_io],
"
// This procedure should never be called, as io.setenv/2 has been
@@ -10336,7 +10358,7 @@ io.setenv(Var, Value) :-
").
:- pragma foreign_proc("Java",
- io.putenv(VarAndValue::in),
+ io.putenv(VarAndValue::in, _Keep::out),
[will_not_call_mercury, tabled_for_io, may_not_duplicate],
"
// This procedure should never be called, as io.setenv/2 has been
@@ -10358,7 +10380,7 @@ io.setenv(Var, Value) :-
").
:- pragma foreign_proc("Erlang",
- io.putenv(VarAndValue::in),
+ io.putenv(VarAndValue::in, _Keep::out),
[will_not_call_mercury, tabled_for_io],
"
% This procedure should never be called, as io.setenv/2 has been
diff --git a/tests/hard_coded/Mmakefile b/tests/hard_coded/Mmakefile
index 1e19c0e..1d10c07 100644
--- a/tests/hard_coded/Mmakefile
+++ b/tests/hard_coded/Mmakefile
@@ -242,6 +242,7 @@ ORDINARY_PROGS = \
promise_eqv_solns_typeclasses \
psqueue_test \
pure_mutable \
+ putenv_gc_fail \
puzzle_detism_bug \
qual_adv_test \
qual_basic_test \
diff --git a/tests/hard_coded/putenv_gc_fail.exp b/tests/hard_coded/putenv_gc_fail.exp
new file mode 100644
index 0000000..154f429
--- /dev/null
+++ b/tests/hard_coded/putenv_gc_fail.exp
@@ -0,0 +1,3 @@
+Use mem: ok
+Use mem: ok
+Got value: bar
diff --git a/tests/hard_coded/putenv_gc_fail.m b/tests/hard_coded/putenv_gc_fail.m
new file mode 100644
index 0000000..7a35958
--- /dev/null
+++ b/tests/hard_coded/putenv_gc_fail.m
@@ -0,0 +1,39 @@
+:- module putenv_gc_fail.
+:- interface.
+
+:- import_module io.
+
+:- pred main(io::di, io::uo) is det.
+
+:- implementation.
+
+:- import_module gc.
+:- import_module int.
+:- import_module list.
+:- import_module maybe.
+:- import_module string.
+
+main(!IO) :-
+ io.set_environment_var("foo", "bar", !IO),
+ use_mem(1000000, !IO),
+ gc.garbage_collect(!IO),
+ use_mem(1000000, !IO),
+ io.get_environment_var("foo", Res, !IO),
+ (
+ Res = yes(Value),
+ io.write_string("Got value: " ++ Value ++ "\n", !IO)
+ ;
+ Res = no,
+ io.write_string("Failure!\n", !IO)
+ ).
+
+:- pred use_mem(int::in, io::di, io::uo) is det.
+
+use_mem(N, !IO) :-
+ io.write_string("Use mem: ", !IO),
+ ( if length(1 `..` N) = N then
+ io.write_string("ok\n", !IO)
+ else
+ io.write_string("hmm\n", !IO)
+ ).
+
More information about the reviews
mailing list