From ec971dcc32af06aad668ed5c4758af3de0b52ad9 Mon Sep 17 00:00:00 2001 From: filoferra <66474175+filoferra@users.noreply.github.com> Date: Wed, 21 Oct 2026 15:15:48 +0200 Subject: [PATCH] Name the C function and PETSc's message in PetscError A PetscError now carries the C function the failing call went to and PETSc's text for the error, read as soon as the call fails, and prints both: "PetscError(56) from VecSetSizes: No support for this operation for this object type. Cannot change/reset vector sizes ...". Before it would only have printed "PetscError(56)". PetscError(code) and err.code work as before. Assisted-by: Claude Opus 5.5 --- CHANGELOG.md | 1 + src/LibPETSc_const.jl | 68 +++++++++++++++++++++++++++++++++++++++++-- test/core/errors.jl | 38 ++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3ea9758b..81523ddb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Added - The handle types are public API, with docstrings: the abstract types to annotate with (`AbstractPetscVec`, `AbstractPetscMat`, `AbstractPetscDM`, `AbstractKSP`, `AbstractPC`, `AbstractSNES`, `AbstractTS`, `AbstractPetscOptions`, `AbstractIS`, `AbstractAO`, `AbstractPF`, `AbstractTao`) and the handles `PetscDM`, `PC`, `IS`, `AO`, `PF` and `Tao`, all reachable as `PETSc.X`. `PETSc.realtype(petsclib)` gives the library's real type. +- A `PetscError` prints the C function the failing call went to and PETSc's message for it, for example `PetscError(56) from VecSetSizes: No support for this operation for this object type. Cannot change/reset vector sizes to 4 local 4 global after previously setting them to 3 local 3 global`, where it printed `PetscError(56)`. The fields `func` and `message` hold the two; `PetscError(code)` still works. ### Fixed diff --git a/src/LibPETSc_const.jl b/src/LibPETSc_const.jl index 60c901be8..ed06fc6d1 100644 --- a/src/LibPETSc_const.jl +++ b/src/LibPETSc_const.jl @@ -3,10 +3,74 @@ const PetscErrorCode = Cint +""" + PetscError(code) + +A PETSc call returned the nonzero error code `code`. `func` is the C function +the failing call went to (`Symbol("")` when not known), and `message` is PETSc's +text for the error: its generic description followed by the specific message +set where the error started, deepest in the call chain. + +# External Links +$(_doc_external("Sys/PetscErrorMessage")) +""" struct PetscError <: Exception code::PetscErrorCode + func::Symbol + message::String +end +PetscError(code) = PetscError(code, Symbol(""), "") + +function Base.showerror(io::IO, err::PetscError) + print(io, "PetscError(", err.code, ")") + isempty(String(err.func)) || print(io, " from ", err.func) + isempty(err.message) || print(io, ": ", err.message) + return nothing +end + +# PETSc's text for error `code`, read right after the failing call: PETSc keeps +# the specific message only until the next error. `library` is what the failing +# `ccall` named. Building an exception must not throw, so a library that can't +# be reached gives an empty message. +@noinline function petsc_error(code, func::Symbol, library) + message = try + petsc_error_message(code, library) + catch + "" + end + return PetscError(code, func, message) end +function petsc_error_message(code, library::AbstractString) + # The library is already loaded, so this only looks up its handle + handle = Libdl.dlopen(library) + fptr = Libdl.dlsym(handle, :PetscErrorMessage) + text, specific = Ref{Ptr{Cchar}}(C_NULL), Ref{Ptr{Cchar}}(C_NULL) + ccall(fptr, PetscErrorCode, (PetscErrorCode, Ptr{Ptr{Cchar}}, Ptr{Ptr{Cchar}}), + code, text, specific) == 0 || return "" + parts = String[unsafe_string(p[]) for p in (text, specific) if p[] != C_NULL] + return join(filter(!isempty, strip.(parts)), ". ") +end +petsc_error_message(_, _) = "" + +# The function name and library of `ccall((:Name, library), ...)`, or nothing +# when the call goes through a function pointer +function ccall_target(expr) + Meta.isexpr(expr, :call) && expr.args[1] === :ccall || return nothing + target = expr.args[2] + Meta.isexpr(target, :tuple) && length(target.args) == 2 || return nothing + name = target.args[1] + name isa QuoteNode && name.value isa Symbol || return nothing + return name.value, target.args[2] +end + +# Throw a `PetscError` when the PETSc call `expr` returns a nonzero code macro chk(expr) - :((errcode = $(esc(expr))) == 0 || throw(PetscError(errcode))) -end \ No newline at end of file + target = ccall_target(expr) + if target === nothing + return :((errcode = $(esc(expr))) == 0 || throw(PetscError(errcode))) + end + name, library = target + return :((errcode = $(esc(expr))) == 0 || + throw(petsc_error(errcode, $(QuoteNode(name)), $(esc(library))))) +end diff --git a/test/core/errors.jl b/test/core/errors.jl index e438d08e8..896521cd7 100644 --- a/test/core/errors.jl +++ b/test/core/errors.jl @@ -93,3 +93,41 @@ MPI.Initialized() || MPI.Init() PETSc.finalize(petsclib) end end + +@testset "PetscError names the C function and PETSc's message" begin + petsclib = PETSc.petsclibs[1] + PETSc.initialize(petsclib) + PetscInt = petsclib.PetscInt + LibPETSc = PETSc.LibPETSc + + v = PETSc.PetscVec(petsclib, 3) + err = try + LibPETSc.VecSetSizes(petsclib, v, PetscInt(4), PetscInt(4)) + catch e + e + end + @test err isa LibPETSc.PetscError + @test err.code == 56 # PETSC_ERR_SUP + @test err.func === :VecSetSizes + @test occursin("Cannot change/reset vector sizes", err.message) + @test_throws "PetscError(56) from VecSetSizes: No support for this operation" throw(err) + + # An error raised deep inside PETSc is named after the call Julia made + err = try + PETSc.DMDA(petsclib, MPI.COMM_SELF, (PETSc.DM_BOUNDARY_NONE,), (10,), 1, 1; da_refine = "abc") + catch e + e + end + @test err.func === :DMSetFromOptions + @test occursin("Input string abc has no integer value", err.message) + + PETSc.destroy!(v) + PETSc.finalize(petsclib) + + # The message is captured when the error is raised, so it still prints + # once the library is finalized + @test occursin("Input string abc", sprint(showerror, err)) + # A code alone prints as it always has + @test sprint(showerror, LibPETSc.PetscError(63)) == "PetscError(63)" + @test LibPETSc.PetscError(63).code == 63 +end