Skip to content

Commit

Permalink
canonicalize Windows environment variables to uppercase (#30593)
Browse files Browse the repository at this point in the history
* canonicalize Windows environment variables to uppercase

* use windows uppercase function

* fix test

* use systemerror in unlikely event of an error

* documentation for case-insensitivity of ENV on Windows.

* use symbol rather than string for `systemerror`
  • Loading branch information
stevengj authored Jan 8, 2019
1 parent 1868f4f commit 6f4eb43
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 3 deletions.
5 changes: 4 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ New library functions
Standard library changes
------------------------


#### LinearAlgebra

* Added keyword arguments `rtol`, `atol` to `pinv` and `nullspace` ([#29998]).
Expand All @@ -42,6 +41,10 @@ Standard library changes

* Fixed `repr` such that it displays `DateTime` as it would be entered in Julia ([#30200]).

#### Miscellaneous

* Since environment variables on Windows are case-insensitive, `ENV` now converts its keys
to uppercase for display, iteration, and copying ([#30593]).

External dependencies
---------------------
Expand Down
16 changes: 15 additions & 1 deletion base/env.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ struct EnvDict <: AbstractDict{String,String}; end
Reference to the singleton `EnvDict`, providing a dictionary interface to system environment
variables.
(On Windows, system environment variables are case-insensitive, and `ENV` correspondingly converts
all keys to uppercase for display, iteration, and copying. Portable code should not rely on the
existence of lower-case environment variables or on the ability to distinguish variables by case.)
"""
const ENV = EnvDict()

Expand All @@ -85,6 +89,16 @@ push!(::EnvDict, kv::Pair{<:AbstractString}) = setindex!(ENV, kv.second, kv.firs

if Sys.iswindows()
GESW() = (pos = ccall(:GetEnvironmentStringsW,stdcall,Ptr{UInt16},()); (pos,pos))
function winuppercase(s::AbstractString)
isempty(s) && return s
LOCALE_INVARIANT = 0x0000007f
LCMAP_UPPERCASE = 0x00000200
ws = transcode(UInt16, String(s))
result = ccall(:LCMapStringW, stdcall, Cint, (UInt32, UInt32, Ptr{UInt16}, Cint, Ptr{UInt16}, Cint),
LOCALE_INVARIANT, LCMAP_UPPERCASE, ws, length(ws), ws, length(ws))
systemerror(:LCMapStringW, iszero(result))
return transcode(String, ws)
end
function iterate(hash::EnvDict, block::Tuple{Ptr{UInt16},Ptr{UInt16}} = GESW())
if unsafe_load(block[1]) == 0
ccall(:FreeEnvironmentStringsW, stdcall, Int32, (Ptr{UInt16},), block[2])
Expand All @@ -100,7 +114,7 @@ if Sys.iswindows()
if m === nothing
error("malformed environment entry: $env")
end
return (Pair{String,String}(m.captures[1], m.captures[2]), (pos+(len+1)*2, blk))
return (Pair{String,String}(winuppercase(m.captures[1]), m.captures[2]), (pos+(len+1)*2, blk))
end
else # !windows
function iterate(::EnvDict, i=0)
Expand Down
23 changes: 22 additions & 1 deletion test/env.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ end
end
@testset "#17956" begin
@test length(ENV) > 1
k1, k2 = "__test__", "__test1__"
k1, k2 = "__TEST__", "__TEST1__"
withenv(k1=>k1, k2=>k2) do
b_k1, b_k2 = false, false
for (k, v) in ENV
Expand Down Expand Up @@ -81,3 +81,24 @@ end
@test ENV["testing_envdict"] == "tested"
delete!(ENV, "testing_envdict")
end

if Sys.iswindows()
@testset "windows case-insensitivity" begin
for k in ("testing_envdict", "testing_envdict_\u00ee")
K = uppercase(k)
v = "tested $k"
ENV[k] = v
@test haskey(ENV, K)
@test ENV[K] == v
@test K in keys(ENV)
@test K in collect(keys(ENV))
@test k collect(keys(ENV))
env = copy(ENV)
@test haskey(env, K)
@test env[K] == v
@test !haskey(env, k)
delete!(ENV, k)
@test !haskey(ENV, K)
end
end
end

0 comments on commit 6f4eb43

Please sign in to comment.