Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve compatibility between Cstring and Ptr. #14024

Merged
merged 4 commits into from
Dec 7, 2015
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions base/c.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,23 @@ else
bitstype 32 Cwstring
end

# construction from typed pointers
convert{T<:Union{Int8,UInt8}}(::Type{Cstring}, p::Ptr{T}) = box(Cstring, p)
convert(::Type{Cwstring}, p::Ptr{Cwchar_t}) = box(Cwstring, p)
convert{T<:Union{Int8,UInt8}}(::Type{Ptr{T}}, p::Cstring) = box(Ptr{T}, p)
convert(::Type{Ptr{Cwchar_t}}, p::Cwstring) = box(Ptr{Cwchar_t}, p)

# construction from untyped pointers
convert{T<:Union{Cstring,Cwstring}}(::Type{T}, p::Ptr{Void}) =
p==C_NULL ? box(Cstring, p) : throw(ArgumentError("cannot convert non-null void pointer to C string"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is ther any problem with converting non-NULL Ptr{Void} to Cstring? Also, isn't this always returning Cstring even when called with Cwstring?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then how would bytestring(Cstring) behave? Currently, it only checks for ptr != C_NULL, relying on the fact that ptr points to a NULL-terminated array of chars. I'm not sure what the advantage would be of allowing Cstring to contain an arbitrary Ptr{Void}

And yes, it only constructs Cstrings... Any suggestion? The constructor was based on @stevengj's comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then how would bytestring(Cstring) behave? Currently, it only checks for ptr != C_NULL, relying on the fact that ptr points to a NULL-terminated array of chars.

I don't see why does this matter. In another word, I don't think it is necessary to make this convert method only defined for invalid pointers. reinterpret already works and if you are adding another method to do the conversion you might as well make them the same. I don't think you will gain any safety by not allowing a non-NULL pointer to be converted to Cstring anyway.

I'm not sure what the advantage would be of allowing Cstring to contain an arbitrary Ptr{Void}

Much easier to inline, less code to write, less code to generate, in the cases the current version is inlined, less work for compiler to remove useless code.

And yes, it only constructs Cstrings... Any suggestion? The constructor was based on @stevengj's comment.

Just replace box(Cstring, p) with box(T, p) as what you would usually do with a parametrized method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, fine by me. I've pushed a commit removing the check and related test.


pointer(p::Cstring) = convert(Ptr{UInt8}, p)
pointer(p::Cwstring) = convert(Ptr{Cwchar_t}, p)

# comparisons against pointers (mainly to support `cstr==C_NULL`)
==(x::Union{Cstring,Cwstring}, y::Ptr) = pointer(x) == y
==(x::Ptr, y::Union{Cstring,Cwstring}) = x == pointer(y)

# here, not in pointer.jl, to avoid bootstrapping problems in coreimg.jl
pointer_to_string(p::Cstring, own::Bool=false) = pointer_to_string(convert(Ptr{UInt8}, p), own)

Expand Down
2 changes: 1 addition & 1 deletion base/env.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This file is a part of Julia. License is MIT: http://julialang.org/license

@unix_only begin
_getenv(var::AbstractString) = ccall(:getenv, Ptr{UInt8}, (Cstring,), var)
_getenv(var::AbstractString) = ccall(:getenv, Cstring, (Cstring,), var)
_hasenv(s::AbstractString) = _getenv(s) != C_NULL
end

Expand Down
6 changes: 3 additions & 3 deletions base/fft/FFTW.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,16 @@ alignment_of(A::FakeArray) = Int32(0)
# FFTW's api/import-wisdom-from-file.c file].

function export_wisdom(fname::AbstractString)
f = ccall(:fopen, Ptr{Void}, (Cstring,Ptr{UInt8}), fname, "w")
f = ccall(:fopen, Ptr{Void}, (Cstring,Cstring), fname, :w)
systemerror("could not open wisdom file $fname for writing", f == C_NULL)
ccall((:fftw_export_wisdom_to_file,libfftw), Void, (Ptr{Void},), f)
ccall(:fputs, Int32, (Ptr{UInt8},Ptr{Void}), " "^256, f)
ccall(:fputs, Int32, (Ptr{UInt8},Ptr{Void}), " "^256, f) # no NUL, hence no Cstring
ccall((:fftwf_export_wisdom_to_file,libfftwf), Void, (Ptr{Void},), f)
ccall(:fclose, Void, (Ptr{Void},), f)
end

function import_wisdom(fname::AbstractString)
f = ccall(:fopen, Ptr{Void}, (Cstring,Ptr{UInt8}), fname, "r")
f = ccall(:fopen, Ptr{Void}, (Cstring,Cstring), fname, :r)
systemerror("could not open wisdom file $fname for reading", f == C_NULL)
if ccall((:fftw_import_wisdom_from_file,libfftw),Int32,(Ptr{Void},),f)==0||
ccall((:fftwf_import_wisdom_from_file,libfftwf),Int32,(Ptr{Void},),f)==0
Expand Down
10 changes: 5 additions & 5 deletions base/file.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ end
cd() = cd(homedir())

@unix_only function cd(f::Function, dir::AbstractString)
fd = ccall(:open,Int32,(Ptr{UInt8},Int32),".",0)
fd = ccall(:open,Int32,(Cstring,Int32),:.,0)
systemerror(:open, fd == -1)
try
cd(dir)
Expand Down Expand Up @@ -177,7 +177,7 @@ end
# Obtain a temporary filename.
function tempname()
d = get(ENV, "TMPDIR", C_NULL) # tempnam ignores TMPDIR on darwin
p = ccall(:tempnam, Ptr{UInt8}, (Ptr{UInt8},Ptr{UInt8}), d, "julia")
p = ccall(:tempnam, Cstring, (Cstring,Cstring), d, :julia)
systemerror(:tempnam, p == C_NULL)
s = bytestring(p)
Libc.free(p)
Expand All @@ -190,15 +190,15 @@ tempdir() = dirname(tempname())
# Create and return the name of a temporary file along with an IOStream
function mktemp(parent=tempdir())
b = joinpath(parent, "tmpXXXXXX")
p = ccall(:mkstemp, Int32, (Ptr{UInt8},), b) # modifies b
p = ccall(:mkstemp, Int32, (Cstring,), b) # modifies b
systemerror(:mktemp, p == -1)
return (b, fdio(p, true))
end

# Create and return the name of a temporary directory
function mktempdir(parent=tempdir())
b = joinpath(parent, "tmpXXXXXX")
p = ccall(:mkdtemp, Ptr{UInt8}, (Ptr{UInt8},), b)
p = ccall(:mkdtemp, Cstring, (Cstring,), b)
systemerror(:mktempdir, p == C_NULL)
return bytestring(p)
end
Expand Down Expand Up @@ -281,7 +281,7 @@ function readdir(path::AbstractString)
offset = 0

for i = 1:file_count
entry = bytestring(ccall(:jl_uv_fs_t_ptr_offset, Ptr{UInt8},
entry = bytestring(ccall(:jl_uv_fs_t_ptr_offset, Cstring,
(Ptr{UInt8}, Int32), uv_readdir_req, offset))
push!(entries, entry)
offset += sizeof(entry) + 1 # offset to the next entry
Expand Down
2 changes: 1 addition & 1 deletion base/gmp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ hex(n::BigInt) = base(16, n)
function base(b::Integer, n::BigInt)
2 <= b <= 62 || throw(ArgumentError("base must be 2 ≤ base ≤ 62, got $b"))
p = ccall((:__gmpz_get_str,:libgmp), Ptr{UInt8}, (Ptr{UInt8}, Cint, Ptr{BigInt}), C_NULL, b, &n)
len = Int(ccall(:strlen, Csize_t, (Ptr{UInt8},), p))
len = Int(ccall(:strlen, Csize_t, (Cstring,), p))
ASCIIString(pointer_to_array(p,len,true))
end

Expand Down
2 changes: 1 addition & 1 deletion base/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ end

function write(io::IO, s::Symbol)
pname = unsafe_convert(Ptr{UInt8}, s)
return write(io, pname, Int(ccall(:strlen, Csize_t, (Ptr{UInt8},), pname)))
return write(io, pname, Int(ccall(:strlen, Csize_t, (Cstring,), pname)))
end

read(s::IO, ::Type{Int8}) = reinterpret(Int8, read(s,UInt8))
Expand Down
4 changes: 2 additions & 2 deletions base/libc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ end
strptime(timestr::AbstractString) = strptime("%c", timestr)
function strptime(fmt::AbstractString, timestr::AbstractString)
tm = TmStruct()
r = ccall(:strptime, Ptr{UInt8}, (Cstring, Cstring, Ptr{TmStruct}),
r = ccall(:strptime, Cstring, (Cstring, Cstring, Ptr{TmStruct}),
timestr, fmt, &tm)
# the following would tell mktime() that this is a local time, and that
# it should try to guess the timezone. not sure if/how this should be
Expand Down Expand Up @@ -163,7 +163,7 @@ end

errno() = ccall(:jl_errno, Cint, ())
errno(e::Integer) = ccall(:jl_set_errno, Void, (Cint,), e)
strerror(e::Integer) = bytestring(ccall(:strerror, Ptr{UInt8}, (Int32,), e))
strerror(e::Integer) = bytestring(ccall(:strerror, Cstring, (Int32,), e))
strerror() = strerror(errno())

@windows_only begin
Expand Down
4 changes: 2 additions & 2 deletions base/libdl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ find_library(libname::Union{Symbol,AbstractString}, extrapaths=ASCIIString[]) =
find_library([string(libname)], extrapaths)

function dlpath(handle::Ptr{Void})
p = ccall(:jl_pathname_for_handle, Ptr{UInt8}, (Ptr{Void},), handle)
p = ccall(:jl_pathname_for_handle, Cstring, (Ptr{Void},), handle)
s = bytestring(p)
@windows_only Libc.free(p)
return s
Expand Down Expand Up @@ -130,7 +130,7 @@ function dllist()

# start at 1 instead of 0 to skip self
for i in 1:numImages-1
name = bytestring(ccall(:_dyld_get_image_name, Ptr{UInt8}, (UInt32,), i))
name = bytestring(ccall(:_dyld_get_image_name, Cstring, (UInt32,), i))
push!(dynamic_libraries, name)
end
end
Expand Down
6 changes: 3 additions & 3 deletions base/libgit2/reference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ end

function shortname(ref::GitReference)
isempty(ref) && return ""
name_ptr = ccall((:git_reference_shorthand, :libgit2), Ptr{UInt8}, (Ptr{Void},), ref.ptr)
name_ptr = ccall((:git_reference_shorthand, :libgit2), Cstring, (Ptr{Void},), ref.ptr)
name_ptr == C_NULL && return ""
return bytestring(name_ptr)
end
Expand All @@ -39,14 +39,14 @@ end
function fullname(ref::GitReference)
isempty(ref) && return ""
reftype(ref) == Consts.REF_OID && return ""
rname = ccall((:git_reference_symbolic_target, :libgit2), Ptr{UInt8}, (Ptr{Void},), ref.ptr)
rname = ccall((:git_reference_symbolic_target, :libgit2), Cstring, (Ptr{Void},), ref.ptr)
rname == C_NULL && return ""
return bytestring(rname)
end

function name(ref::GitReference)
isempty(ref) && return ""
name_ptr = ccall((:git_reference_name, :libgit2), Ptr{UInt8}, (Ptr{Void},), ref.ptr)
name_ptr = ccall((:git_reference_name, :libgit2), Cstring, (Ptr{Void},), ref.ptr)
name_ptr == C_NULL && return ""
return bytestring(name_ptr)
end
Expand Down
1 change: 1 addition & 0 deletions base/mpfr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,7 @@ function string(x::BigFloat)
k = ceil(Int32, precision(x) * 0.3010299956639812)
lng = k + Int32(8) # Add space for the sign, the most significand digit, the dot and the exponent
buf = Array(UInt8, lng + 1)
# format strings are guaranteed to contain no NUL, so we don't use Cstring
lng = ccall((:mpfr_snprintf,:libmpfr), Int32, (Ptr{UInt8}, Culong, Ptr{UInt8}, Ptr{BigFloat}...), buf, lng + 1, "%.Re", &x)
if lng < k + 5 # print at least k decimal places
lng = ccall((:mpfr_sprintf,:libmpfr), Int32, (Ptr{UInt8}, Ptr{UInt8}, Ptr{BigFloat}...), buf, "%.$(k)Re", &x)
Expand Down
2 changes: 1 addition & 1 deletion base/pointer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function pointer_to_string(p::Ptr{UInt8}, len::Integer, own::Bool=false)
ccall(:jl_array_to_string, Any, (Any,), a)::ByteString
end
pointer_to_string(p::Ptr{UInt8}, own::Bool=false) =
pointer_to_string(p, ccall(:strlen, Csize_t, (Ptr{UInt8},), p), own)
pointer_to_string(p, ccall(:strlen, Csize_t, (Cstring,), p), own)

# convert a raw Ptr to an object reference, and vice-versa
unsafe_pointer_to_objref(x::Ptr) = ccall(:jl_value_ptr, Any, (Ptr{Void},), x)
Expand Down
4 changes: 2 additions & 2 deletions base/printf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,7 @@ function decode(b::Int, x::BigInt)
pt = Base.ndigits(x, abs(b))
length(DIGITS) < pt+1 && resize!(DIGITS, pt+1)
neg && (x.size = -x.size)
ccall((:__gmpz_get_str, :libgmp), Ptr{UInt8},
ccall((:__gmpz_get_str, :libgmp), Cstring,
(Ptr{UInt8}, Cint, Ptr{BigInt}), DIGITS, b, &x)
neg && (x.size = -x.size)
return Int32(pt), Int32(pt), neg
Expand All @@ -876,7 +876,7 @@ function decode_0ct(x::BigInt)
length(DIGITS) < pt+1 && resize!(DIGITS, pt+1)
neg && (x.size = -x.size)
p = convert(Ptr{UInt8}, DIGITS) + 1
ccall((:__gmpz_get_str, :libgmp), Ptr{UInt8},
ccall((:__gmpz_get_str, :libgmp), Cstring,
(Ptr{UInt8}, Cint, Ptr{BigInt}), p, 8, &x)
neg && (x.size = -x.size)
return neg, Int32(pt), Int32(pt)
Expand Down
2 changes: 1 addition & 1 deletion base/serialize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function serialize(s::SerializationState, x::Symbol)
return write_as_tag(s.io, tag)
end
pname = unsafe_convert(Ptr{UInt8}, x)
ln = Int(ccall(:strlen, Csize_t, (Ptr{UInt8},), pname))
ln = Int(ccall(:strlen, Csize_t, (Cstring,), pname))
if ln <= 255
writetag(s.io, SYMBOL_TAG)
write(s.io, UInt8(ln))
Expand Down
4 changes: 2 additions & 2 deletions base/stream.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1017,8 +1017,8 @@ type UVError <: Exception
UVError(p::AbstractString,code::Integer)=new(p,code)
end

struverror(err::UVError) = bytestring(ccall(:uv_strerror,Ptr{UInt8},(Int32,),err.code))
uverrorname(err::UVError) = bytestring(ccall(:uv_err_name,Ptr{UInt8},(Int32,),err.code))
struverror(err::UVError) = bytestring(ccall(:uv_strerror,Cstring,(Int32,),err.code))
uverrorname(err::UVError) = bytestring(ccall(:uv_err_name,Cstring,(Int32,),err.code))

uv_error(prefix::Symbol, c::Integer) = uv_error(string(prefix),c)
uv_error(prefix::AbstractString, c::Integer) = c < 0 ? throw(UVError(prefix,c)) : nothing
Expand Down
2 changes: 1 addition & 1 deletion base/strings/basic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ bytestring(s::Vector{UInt8}) = bytestring(pointer(s),length(s))

function bytestring(p::Union{Ptr{UInt8},Ptr{Int8}})
p == C_NULL ? throw(ArgumentError("cannot convert NULL to string")) :
ccall(:jl_cstr_to_string, Any, (Ptr{UInt8},), p)::ByteString
ccall(:jl_cstr_to_string, Any, (Cstring,), p)::ByteString
end
bytestring(s::Cstring) = bytestring(convert(Ptr{UInt8}, s))

Expand Down
2 changes: 1 addition & 1 deletion base/unicode/utf8proc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function utf8proc_map(s::ByteString, flags::Integer)
result = ccall(:utf8proc_map, Cssize_t,
(Ptr{UInt8}, Cssize_t, Ref{Ptr{UInt8}}, Cint),
s, sizeof(s), p, flags)
result < 0 && error(bytestring(ccall(:utf8proc_errmsg, Ptr{UInt8},
result < 0 && error(bytestring(ccall(:utf8proc_errmsg, Cstring,
(Cssize_t,), result)))
pointer_to_string(p[], result, true)::ByteString
end
Expand Down
34 changes: 21 additions & 13 deletions doc/manual/calling-c-and-fortran-code.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,36 +80,38 @@ is that a 1-tuple must be written with a trailing comma. For
example, to call the ``getenv`` function to get a pointer to the value
of an environment variable, one makes a call like this::

julia> path = ccall((:getenv, "libc"), Ptr{UInt8}, (Ptr{UInt8},), "SHELL")
Ptr{UInt8} @0x00007fff5fbffc45
julia> path = ccall((:getenv, "libc"), Cstring, (Cstring,), "SHELL")
Cstring(@0x00007fff5fbffc45)

julia> bytestring(path)
"/bin/bash"

Note that the argument type tuple must be written as ``(Ptr{UInt8},)``,
rather than ``(Ptr{UInt8})``. This is because ``(Ptr{UInt8})`` is just
the expression ``Ptr{UInt8}`` surrounded by parentheses, rather than
a 1-tuple containing ``Ptr{UInt8}``::
Note that the argument type tuple must be written as ``(Cstring,)``,
rather than ``(Cstring)``. This is because ``(Cstring)`` is just
the expression ``Cstring`` surrounded by parentheses, rather than
a 1-tuple containing ``Cstring``:

julia> (Ptr{UInt8})
Ptr{UInt8}
.. doctest::
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guess these could be doctests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Do I need to do anything after converting the block to .. doctest::? The file doesn't seem to get picked up by make -C doc doctest

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh strange, maybe it's not a big enough deal to be worth worrying about then

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake, I didn't realize :: started a literal block. Will update the documentation commit in a sec.


julia> (Ptr{UInt8},)
(Ptr{UInt8},)
julia> (Cstring)
Cstring

julia> (Cstring,)
(Cstring,)

In practice, especially when providing reusable functionality, one
generally wraps ``ccall`` uses in Julia functions that set up arguments
and then check for errors in whatever manner the C or Fortran function
indicates them, propagating to the Julia caller as exceptions. This is
especially important since C and Fortran APIs are notoriously
inconsistent about how they indicate error conditions. For example, the
``getenv`` C library function is wrapped in the following Julia function
in
``getenv`` C library function is wrapped in the following Julia function,
which is a simplified version of the actual definition from
`env.jl <https://github.com/JuliaLang/julia/blob/master/base/env.jl>`_::

function getenv(var::AbstractString)
val = ccall((:getenv, "libc"),
Ptr{UInt8}, (Ptr{UInt8},), var)
Cstring, (Cstring,), var)
if val == C_NULL
error("getenv: undefined variable: ", var)
end
Expand Down Expand Up @@ -148,6 +150,10 @@ C libraries to use this pattern of requiring the caller to allocate
memory to be passed to the callee and filled in. Allocation of memory
from Julia like this is generally accomplished by creating an
uninitialized array and passing a pointer to its data to the C function.
This is why we don't use the ``Cstring`` type here: as the array is
uninitialized, it could contain NUL bytes. Converting to a ``Cstring`` as
part of the ``ccall`` checks for contained NUL bytes and could therefore
throw a conversion error.

Creating C-Compatible Julia Function Pointers
---------------------------------------------
Expand Down Expand Up @@ -421,6 +427,8 @@ error if the Julia string contains any embedded NUL characters (which would caus
truncated if the C routine treats NUL as the terminator). If you are passing a ``char*`` to a C routine that
does not assume NUL termination (e.g. because you pass an explicit string length), or if you know for certain that
your Julia string does not contain NUL and want to skip the check, you can use ``Ptr{UInt8}`` as the argument type.
``Cstring`` can also be used as the `ccall` return type, but in that case it obviously does not introduce any extra
checks and is only meant to improve readability of the call.

**System-dependent:**

Expand Down
28 changes: 27 additions & 1 deletion test/strings/types.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is a part of Julia. License is MIT: http://julialang.org/license

## SubString, RevString, and RepString tests ##
## SubString, RevString, RepString and Cstring tests ##

## SubString tests ##
u8str = "∀ ε > 0, ∃ δ > 0: |x-y| < δ ⇒ |f(x)-f(y)| < ε"
Expand Down Expand Up @@ -213,3 +213,29 @@ let
@test srep[7] == 'β'
@test_throws BoundsError srep[8]
end


## Cstring tests ##

# issue #13974: comparison against pointers

str = bytestring("foobar")
ptr = pointer(str)
cstring = Cstring(ptr)
@test ptr == cstring
@test cstring == ptr

# convenient NULL string creation from Ptr{Void}
nullstr = Cstring(C_NULL)
# but make sure other un/mistyped pointers are rejected
@test_throws ArgumentError Cstring(Ptr{Void}(ptr))

# Comparisons against NULL strings
@test ptr != nullstr
@test nullstr != ptr

# Short-hand comparison against C_NULL
@test nullstr == C_NULL
@test C_NULL == nullstr
@test cstring != C_NULL
@test C_NULL != cstring