Skip to content

Commit cb6bf1c

Browse files
authored
Fix "Fix ccall with no supplied varargs" (#118)
1 parent c848fe4 commit cb6bf1c

File tree

4 files changed

+61
-37
lines changed

4 files changed

+61
-37
lines changed

src/desugaring.jl

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,6 +1706,16 @@ function expand_kw_call(ctx, srcref, farg, args, kws)
17061706
]
17071707
end
17081708

1709+
# Special rule: Any becomes core.Any regardless of the module
1710+
# scope, and don't need GC roots.
1711+
function expand_ccall_argtype(ctx, ex)
1712+
if is_same_identifier_like(ex, "Any")
1713+
@ast ctx ex "Any"::K"core"
1714+
else
1715+
expand_forms_2(ctx, ex)
1716+
end
1717+
end
1718+
17091719
# Expand the (sym,lib) argument to ccall/cglobal
17101720
function expand_C_library_symbol(ctx, ex)
17111721
expanded = expand_forms_2(ctx, ex)
@@ -1749,44 +1759,36 @@ function expand_ccall(ctx, ex)
17491759
end
17501760
arg_types = children(arg_type_tuple)
17511761
vararg_type = nothing
1752-
num_required_args = length(arg_types)
17531762
if length(arg_types) >= 1
17541763
va = arg_types[end]
17551764
if kind(va) == K"..."
17561765
@chk numchildren(va) == 1
17571766
# Ok: vararg function
1758-
vararg_type = va
1759-
if length(arg_types) <= 1
1760-
throw(LoweringError(vararg_type, "C ABI prohibits vararg without one required argument"))
1761-
else
1762-
num_required_args = length(arg_types) - 1
1767+
vararg_type = expand_ccall_argtype(ctx, va[1])
1768+
arg_types = arg_types[1:end-1]
1769+
if length(arg_types) === 0
1770+
throw(LoweringError(va, "C ABI prohibits vararg without one required argument"))
17631771
end
17641772
end
17651773
end
17661774
# todo: use multi-range errors here
1767-
if length(args) < num_required_args
1775+
if length(args) < length(arg_types)
17681776
throw(LoweringError(ex, "Too few arguments in ccall compared to argument types"))
17691777
elseif length(args) > length(arg_types) && isnothing(vararg_type)
17701778
throw(LoweringError(ex, "More arguments than types in ccall"))
17711779
end
17721780
sctx = with_stmts(ctx)
17731781
expanded_types = SyntaxList(ctx)
1774-
for (i, argt) in enumerate(arg_types)
1782+
for argt in arg_types
17751783
if kind(argt) == K"..."
1776-
if i == length(arg_types)
1777-
argt = argt[1]
1778-
else
1779-
throw(LoweringError(argt, "only the trailing ccall argument type should have `...`"))
1780-
end
1781-
end
1782-
if is_same_identifier_like(argt, "Any")
1783-
# Special rule: Any becomes core.Any regardless of the module
1784-
# scope, and don't need GC roots.
1785-
argt = @ast ctx argt "Any"::K"core"
1784+
throw(LoweringError(argt, "only the trailing ccall argument type should have `...`"))
17861785
end
1787-
push!(expanded_types, expand_forms_2(ctx, argt))
1786+
push!(expanded_types, expand_ccall_argtype(ctx, argt))
17881787
end
1789-
#
1788+
for _ in length(arg_types)+1:length(args)
1789+
push!(expanded_types, vararg_type)
1790+
end
1791+
17901792
# An improvement might be wrap the use of types in cconvert in a special
17911793
# K"global_scope" expression which modifies the scope resolution. This
17921794
# would at least make the rules self consistent if not pretty.
@@ -1843,7 +1845,7 @@ function expand_ccall(ctx, ex)
18431845
expanded_types...
18441846
]
18451847
]
1846-
(isnothing(vararg_type) ? 0 : num_required_args)::K"Integer"
1848+
(isnothing(vararg_type) ? 0 : length(arg_types))::K"Integer"
18471849
if isnothing(cconv)
18481850
"ccall"::K"Symbol"
18491851
else

test/function_calls_ir.jl

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -406,14 +406,15 @@ ccall(:printf, Cint, (Cstring, Cstring...), "%s = %s\n", "2 + 2", "5")
406406
#---------------------
407407
1 TestMod.Cstring
408408
2 TestMod.Cstring
409-
3 (call top.cconvert %"%s = %s\n")
410-
4 (call top.cconvert %"2 + 2")
411-
5 (call top.cconvert %"5")
412-
6 (call top.unsafe_convert %%)
413-
7 (call top.unsafe_convert % %₄)
409+
3 TestMod.Cstring
410+
4 (call top.cconvert %"%s = %s\n")
411+
5 (call top.cconvert %"2 + 2")
412+
6 (call top.cconvert %"5")
413+
7 (call top.unsafe_convert % %₄)
414414
8 (call top.unsafe_convert %%₅)
415-
9 (foreigncall :printf (static_eval TestMod.Cint) (static_eval (call core.svec TestMod.Cstring TestMod.Cstring TestMod.Cstring)) 1 :ccall %%%%%%₅)
416-
10 (return %₉)
415+
9 (call top.unsafe_convert %%₆)
416+
10 (foreigncall :printf (static_eval TestMod.Cint) (static_eval (call core.svec TestMod.Cstring TestMod.Cstring TestMod.Cstring)) 1 :ccall %%%%%%₆)
417+
11 (return %₁₀)
417418

418419
########################################
419420
# Error: ccall with too few arguments

test/misc.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,28 @@ end
4747
@test JuliaLowering.include_string(test_mod, """
4848
ccall(:strlen, Csize_t, (Cstring,), "asdfg")
4949
""") == 5
50+
@test JuliaLowering.include_string(test_mod, """
51+
function cvarargs_0()
52+
strp = Ref{Ptr{Cchar}}(0)
53+
fmt = "hi"
54+
len = ccall(:asprintf, Cint, (Ptr{Ptr{Cchar}}, Cstring, Cfloat...), strp, fmt)
55+
str = unsafe_string(strp[], len)
56+
Libc.free(strp[])
57+
return str
58+
end
59+
""") isa Function
60+
@test test_mod.cvarargs_0() == "hi"
61+
@test JuliaLowering.include_string(test_mod, """
62+
function cvarargs_2(arg1::Float64, arg2::Float64)
63+
strp = Ref{Ptr{Cchar}}(0)
64+
fmt = "%3.1f %3.1f"
65+
len = ccall(:asprintf, Cint, (Ptr{Ptr{Cchar}}, Cstring, Cfloat...), strp, fmt, arg1, arg2)
66+
str = unsafe_string(strp[], len)
67+
Libc.free(strp[])
68+
return str
69+
end
70+
""") isa Function
71+
@test test_mod.cvarargs_2(1.1, 2.2) == "1.1 2.2"
5072

5173
# cfunction
5274
JuliaLowering.include_string(test_mod, """

test/misc_ir.jl

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -455,15 +455,14 @@ ccall(:fcntl, Cint, (RawFD, Cint, Cint...), s, F_GETFL)
455455
#---------------------
456456
1 TestMod.RawFD
457457
2 TestMod.Cint
458-
3 TestMod.Cint
459-
4 TestMod.s
460-
5 (call top.cconvert %%₄)
461-
6 TestMod.F_GETFL
462-
7 (call top.cconvert %%₆)
463-
8 (call top.unsafe_convert %%₅)
464-
9 (call top.unsafe_convert %%₇)
465-
10 (foreigncall :fcntl (static_eval TestMod.Cint) (static_eval (call core.svec TestMod.RawFD TestMod.Cint TestMod.Cint)) 2 :ccall %%%%₇)
466-
11 (return %₁₀)
458+
3 TestMod.s
459+
4 (call top.cconvert %%₃)
460+
5 TestMod.F_GETFL
461+
6 (call top.cconvert %%₅)
462+
7 (call top.unsafe_convert %%₄)
463+
8 (call top.unsafe_convert %%₆)
464+
9 (foreigncall :fcntl (static_eval TestMod.Cint) (static_eval (call core.svec TestMod.RawFD TestMod.Cint)) 2 :ccall %%%%₆)
465+
10 (return %₉)
467466

468467
########################################
469468
# Error: No return annotation on @ccall

0 commit comments

Comments
 (0)