Skip to content

BREAKING: Check MATLAB exceptions #237

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 1 addition & 5 deletions src/MATLAB.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,7 @@ else
)
end

# exceptions
struct MEngineError <: Exception
message::String
end

include("exceptions.jl")
include("init.jl") # initialize Refs
include("mxarray.jl")
include("matfile.jl")
Expand Down
52 changes: 49 additions & 3 deletions src/engine.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ mutable struct MSession
ptr::Ptr{Cvoid}
buffer::Vector{UInt8}
bufptr::Ptr{UInt8}
check_exceptions::Bool

function MSession(bufsize::Integer=default_output_buffer_size; flags=default_startflag)
function MSession(
bufsize::Integer=default_output_buffer_size;
flags=default_startflag,
check_exceptions::Bool=true,
)
if Sys.iswindows()
assign_persistent_msession()
end
Expand Down Expand Up @@ -56,7 +61,7 @@ mutable struct MSession
bufptr = convert(Ptr{UInt8}, C_NULL)
end

self = new(ep, buf, bufptr)
self = new(ep, buf, bufptr, check_exceptions)
finalizer(release, self)
return self
end
Expand Down Expand Up @@ -85,6 +90,13 @@ function close(session::MSession)
return nothing
end

has_exception_check_enabled(session::MSession=get_default_msession()) =
session.check_exceptions
disable_exception_check!(session::MSession=get_default_msession()) =
(session.check_exceptions = false; nothing)
enable_exception_check!(session::MSession=get_default_msession()) =
(session.check_exceptions = true; nothing)

# default session

const default_msession_ref = Ref{MSession}()
Expand Down Expand Up @@ -137,7 +149,7 @@ end
#
###########################################################

function eval_string(session::MSession, stmt::String)
function _eval_string(session::MSession, stmt::String)
# evaluate a MATLAB statement in a given MATLAB session
ret = ccall(eng_eval_string[], Cint, (Ptr{Cvoid}, Ptr{UInt8}), session, stmt)
ret != 0 && throw(MEngineError("invalid engine session (err = $ret)"))
Expand All @@ -152,6 +164,13 @@ function eval_string(session::MSession, stmt::String)
return nothing
end

function eval_string(session::MSession, stmt::String)
_eval_string(session, stmt)
if session.check_exceptions
check_and_clear_last_exception(session)
end
end

eval_string(stmt::String) = eval_string(get_default_msession(), stmt)

function put_variable(session::MSession, name::Symbol, v::MxArray)
Expand Down Expand Up @@ -192,6 +211,33 @@ get_mvariable(name::Symbol) = get_mvariable(get_default_msession(), name)
get_variable(name::Symbol) = jvalue(get_mvariable(name))
get_variable(name::Symbol, kind) = jvalue(get_mvariable(name), kind)

"""
check_and_clear_last_exception(session::MSession)

Checks if an exception has been thrown in the MATLAB session by checking the `MException.last` variable.
If it is not empty, it throws a `MatlabException` with the message and identifier of the last exception.
In any case, it clears the `MException.last` variable.
"""
function check_and_clear_last_exception(session::MSession)
exception_check_code = """
matlab_exception_jl_message = MException.last.message;
matlab_exception_jl_identifier = MException.last.identifier;
MException.last('reset');
"""
_eval_string(session, exception_check_code)
message = jvalue(get_mvariable(session, :matlab_exception_jl_message))
identifier = jvalue(get_mvariable(session, :matlab_exception_jl_identifier))

if !isempty(identifier)
throw(MatlabException(identifier, message))
end

_eval_string(
session,
"clear matlab_exception_jl_message matlab_exception_jl_identifier;",
)
end

###########################################################
#
# macro to simplify syntax
Expand Down
14 changes: 14 additions & 0 deletions src/exceptions.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
struct MEngineError <: Exception
message::String
end

"""
MEngineError(message::String)

Exception thrown by MATLAB, e.g. due to syntax errors in the code
passed to `eval_string` or `mat"..."`.
"""
struct MatlabException <: Exception
identifier::String
message::String
end
30 changes: 30 additions & 0 deletions test/matstr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,33 @@ $d ...
# Test strings with =
text = "hello = world"
@test mat"strfind($text, 'o = w')" == 5

@testset "Propagate Matlab Exceptions" begin

# Checks should be enabled by default
@test MATLAB.has_exception_check_enabled() == true

# Test invalid command
@test_throws MATLAB.MatlabException mat"invalid_command"

# Test invalid assignment
@test_throws MATLAB.MatlabException mat"1 = 2"

# Test invalid command within a block
@test_throws MATLAB.MatlabException mat"""
xyz = 1 + 2;
invalid_command;
abc = 2 * xyz;
"""

# Disable Checks
MATLAB.disable_exception_check!()
@test MATLAB.has_exception_check_enabled() == false

# Test invalid command
try
mat"invalid_command"
catch ex
@test false # should not throw an exception
end
end
Loading