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

More libgit2 memory ownership issues #24464

Open
Keno opened this issue Nov 3, 2017 · 0 comments
Open

More libgit2 memory ownership issues #24464

Keno opened this issue Nov 3, 2017 · 0 comments
Labels
help wanted Indicates that a maintainer wants help on an issue or pull request libgit2 The libgit2 library or the LibGit2 stdlib module

Comments

@Keno
Copy link
Member

Keno commented Nov 3, 2017

  • function Base.getindex(blame::GitBlame, i::Integer)
    if !(1 <= i <= counthunks(blame))
    throw(BoundsError(blame, (i,)))
    end
    hunk_ptr = ccall((:git_blame_get_hunk_byindex, :libgit2),
    Ptr{BlameHunk},
    (Ptr{Void}, Csize_t), blame.ptr, i-1)
    return unsafe_load(hunk_ptr)
    end

    There's two issues here:
    a) hunk_ptr points into blame, so the former needs to be scoped to the latter's lifetime
    b) The signature objects inside the loaded BlameHunk are owned by blame, so they need to be duplicated or otherwise scoped to blame.

ptr = ccall((:git_blob_rawcontent, :libgit2), Ptr{UInt8}, (Ptr{Void},), blob.ptr)

ptr needs to be kept alive until the copy is done

  • msg_ptr = raw ? ccall((:git_commit_message_raw, :libgit2), Cstring, (Ptr{Void},), c.ptr) :
    ccall((:git_commit_message, :libgit2), Cstring, (Ptr{Void},), c.ptr)
    if msg_ptr == C_NULL
    return nothing
    end
    return unsafe_string(msg_ptr)

msg_ptr needs to be scoped to c

  • function author(c::GitCommit)
    ptr = ccall((:git_commit_author, :libgit2), Ptr{SignatureStruct}, (Ptr{Void},), c.ptr)
    @assert ptr != C_NULL
    return Signature(ptr)
    end

The resulting signature needs to be duplicated or scoped to c

  • function Base.start(ci::GitConfigIter)
    entry_ptr_ptr = Ref{Ptr{ConfigEntry}}(C_NULL)
    err = ccall((:git_config_next, :libgit2), Cint,
    (Ptr{Ptr{ConfigEntry}}, Ptr{Void}), entry_ptr_ptr, ci.ptr)
    if err == Cint(Error.GIT_OK)
    state = Nullable{ConfigEntry}(unsafe_load(entry_ptr_ptr[]))
    elseif err == Cint(Error.ITEROVER)
    state = Nullable{ConfigEntry}()
    else
    throw(GitError(err))
    end
    return state
    end
    Base.done(ci::GitConfigIter, state) = isnull(state)
    function Base.next(ci::GitConfigIter, state)
    entry = Base.get(state)
    entry_ptr_ptr = Ref{Ptr{ConfigEntry}}(C_NULL)
    err = ccall((:git_config_next, :libgit2), Cint,
    (Ptr{Ptr{ConfigEntry}}, Ptr{Void}), entry_ptr_ptr, ci.ptr)
    if err == Cint(Error.GIT_OK)
    state = Nullable{ConfigEntry}(unsafe_load(entry_ptr_ptr[]))
    elseif err == Cint(Error.ITEROVER)
    state = Nullable{ConfigEntry}()
    else
    throw(GitError(err))
    end
    return (entry, state)
    end

I suspect this is ok for regular uses of the iteration protocol, but it would be good to scope the ConfigEntry to the iterator that produced it.

  • julia/base/libgit2/diff.jl

    Lines 113 to 121 in 8cd97c5

    function Base.getindex(diff::GitDiff, i::Integer)
    if i < 1 || i > count(diff)
    throw(BoundsError(diff, (i,)))
    end
    delta_ptr = ccall((:git_diff_get_delta, :libgit2),
    Ptr{DiffDelta},
    (Ptr{Void}, Csize_t), diff.ptr, i-1)
    return unsafe_load(delta_ptr)
    end

Two bugs:
a) delta_ptr needs to be scoped to the diff
b) the file names inside the Delta are scoped to the diff as well (see https://github.com/libgit2/libgit2/blob/master/src/diff_generate.c#L47).
Probably easiest to duplicate these rather than keep the diff around.

  • julia/base/libgit2/index.jl

    Lines 170 to 176 in 8cd97c5

    function Base.getindex(idx::GitIndex, i::Integer)
    ie_ptr = ccall((:git_index_get_byindex, :libgit2),
    Ptr{IndexEntry},
    (Ptr{Void}, Csize_t), idx.ptr, i-1)
    ie_ptr == C_NULL && return nothing
    return unsafe_load(ie_ptr)
    end

Same issue.

The result of the ccall is an interior pointer into the AnnotatedCommit, so we need to make sure to keep that alive until after the unsafe load

Need to keep the array alive until the hash is created (or even better let the array propagate to ccall, so ccall will do it for you).

Keep the git reference alive until the string is created

  • function Base.getindex(rb::GitRebase, i::Integer)
    if !(1 <= i <= count(rb))
    throw(BoundsError(rb, (i,)))
    end
    rb_op_ptr = ccall((:git_rebase_operation_byindex, :libgit2),
    Ptr{RebaseOperation},
    (Ptr{Void}, Csize_t), rb.ptr, i-1)
    return unsafe_load(rb_op_ptr)
    end

Same issue as the other getindex methods

  • function Base.next(rb::GitRebase)
    rb_op_ptr_ptr = Ref{Ptr{RebaseOperation}}(C_NULL)
    try
    @check ccall((:git_rebase_next, :libgit2), Cint,
    (Ptr{Ptr{RebaseOperation}}, Ptr{Void}),
    rb_op_ptr_ptr, rb.ptr)
    catch err
    err.code == Error.ITEROVER && return nothing
    rethrow(err)
    end
    return unsafe_load(rb_op_ptr_ptr[])
    end

Same issue

  • function name(ref::GitReference)
    isempty(ref) && return ""
    name_ptr = ccall((:git_reference_name, :libgit2), Cstring, (Ptr{Void},), ref.ptr)
    name_ptr == C_NULL && return ""
    return unsafe_string(name_ptr)
    end
    function branch(ref::GitReference)
    isempty(ref) && return ""
    str_ptr_ptr = Ref{Cstring}()
    @check ccall((:git_branch_name, :libgit2), Cint,
    (Ptr{Cstring}, Ptr{Void},), str_ptr_ptr, ref.ptr)
    return unsafe_string(str_ptr_ptr[])
    end

Object needs to be kept alive until the string result is created

  • function url(rmt::GitRemote)
    url_ptr = ccall((:git_remote_url, :libgit2), Cstring, (Ptr{Void},), rmt.ptr)
    url_ptr == C_NULL && return ""
    return unsafe_string(url_ptr)
    end
    """
    push_url(rmt::GitRemote)
    Get the push URL of a remote git repository.
    # Examples
    ```julia-repl
    julia> repo_url = "https://github.com/JuliaLang/Example.jl";
    julia> repo = LibGit2.init(mktempdir());
    julia> LibGit2.set_remote_push_url(repo, "origin", repo_url);
    julia> LibGit2.push_url(LibGit2.get(LibGit2.GitRemote, repo, "origin"))
    "https://github.com/JuliaLang/Example.jl"
    ```
    """
    function push_url(rmt::GitRemote)
    url_ptr = ccall((:git_remote_pushurl, :libgit2), Cstring, (Ptr{Void},), rmt.ptr)
    url_ptr == C_NULL && return ""
    return unsafe_string(url_ptr)
    end
    """
    name(rmt::GitRemote)
    Get the name of a remote repository, for instance `"origin"`.
    If the remote is anonymous (see [`GitRemoteAnon`](@ref))
    the name will be an empty string `""`.
    # Examples
    ```julia-repl
    julia> repo_url = "https://github.com/JuliaLang/Example.jl";
    julia> repo = LibGit2.clone(cache_repo, "test_directory");
    julia> remote = LibGit2.GitRemote(repo, "origin", repo_url);
    julia> name(remote)
    "origin"
    ```
    """
    function name(rmt::GitRemote)
    name_ptr = ccall((:git_remote_name, :libgit2), Cstring, (Ptr{Void},), rmt.ptr)
    name_ptr == C_NULL && return ""
    return unsafe_string(name_ptr)
    end

Same in these four functions

  • function Base.getindex(status::GitStatus, i::Integer)
    1 <= i <= length(status) || throw(BoundsError())
    entry_ptr = ccall((:git_status_byindex, :libgit2),
    Ptr{StatusEntry},
    (Ptr{Void}, Csize_t),
    status.ptr, i-1)
    entry_ptr == C_NULL && throw(Error.GitError(Error.ERROR))
    return unsafe_load(entry_ptr)
    end

The usual

  • """
    LibGit2.name(tag::GitTag)
    The name of `tag` (e.g. `"v0.5"`).
    """
    function name(tag::GitTag)
    str_ptr = ccall((:git_tag_name, :libgit2), Cstring, (Ptr{Void},), tag.ptr)
    str_ptr == C_NULL && throw(Error.GitError(Error.ERROR))
    return unsafe_string(str_ptr)
    end
    # should we return the actual object? i.e. git_tag_target?
    """
    LibGit2.target(tag::GitTag)
    The `GitHash` of the target object of `tag`.
    """
    function target(tag::GitTag)
    oid_ptr = ccall((:git_tag_target_id, :libgit2), Ptr{GitHash}, (Ptr{Void},), tag.ptr)
    oid_ptr == C_NULL && throw(Error.GitError(Error.ERROR))
    return unsafe_load(oid_ptr)
    end

The usual

  • function entryid(te::GitTreeEntry)
    oid_ptr = ccall((:git_tree_entry_id, :libgit2), Ptr{UInt8}, (Ptr{Void},), te.ptr)
    return GitHash(oid_ptr)
    end

oid_ptr needs to be appropriately scoped

And anything else I might have missed

@ararslan ararslan added the libgit2 The libgit2 library or the LibGit2 stdlib module label Nov 3, 2017
@Keno Keno added the help wanted Indicates that a maintainer wants help on an issue or pull request label Nov 3, 2017
@vtjnash vtjnash added this to the 1.0 milestone Jul 8, 2018
@JeffBezanson JeffBezanson modified the milestones: 1.0, 1.0.x Jul 26, 2018
@DilumAluthge DilumAluthge removed this from the 1.x.y milestone Mar 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Indicates that a maintainer wants help on an issue or pull request libgit2 The libgit2 library or the LibGit2 stdlib module
Projects
None yet
Development

No branches or pull requests

5 participants