ERROR: UndefRefError: access to undefined reference in debug mode

Usually when I debug code using JuliaInterpreter I can see the variables from the current scope in the workspace and I can also use them in the REPL (to evaluate certain expressions).
I wanted to run the code from: https://github.com/r3tex/ObjectDetector.jl and run into some error so I decided to debug it, but the problem is whenever I want to copy paste some line in the REPL in debug mode I get UndefRefError for variables, although I can see them in the workspace.

Unfortunately I haven’t been able to generate a MWE without the need of loading the weights and cfg files for yolov3 from https://pjreddie.com/media/files/yolov3.weights and https://github.com/r3tex/ObjectDetector.jl/blob/master/data/yolov3.cfg

using Flux, JuliaInterpreter
in(:CuArrays, names(Main, imported = true)) && (using CuArrays; CuArrays.allowscalar(false))

# convert config String values into native Julia types
# not type safe, but not performance critical
function cfgparse(val::AbstractString)
    if all(isletter, val)
        return val::AbstractString
    else
        return out = occursin('.', val) ? parse(Float64, val) : parse(Int64, val)
    end
end

# split config String into a key and value part
# split value into array if necessary
function cfgsplit(dat::String)
    name, values = split(dat, '=')
    values = split(values, ',')
    k = Symbol(strip(name))
    v = length(values) == 1 ? cfgparse(values[1]) : [cfgparse(v) for v in values]
    return k::Symbol => v::Any
end

# read config file and return an array of settings
function cfgread(file::String)
    data = reverse(filter(d -> length(d) > 0 && d[1] != '#', readlines(file)))
    out = Array{Pair{Symbol, Dict{Symbol, Any}}, 1}(undef, 0)
    settings = Dict{Symbol, Any}()
    for row in data
        if row[1] == '['
            push!(out, Symbol(row[2:end-1]) => settings)
            settings = Dict{Symbol, Any}()
        else
            push!(settings, cfgsplit(row))
        end
    end
    return reverse(out)::Array{Pair{Symbol, Dict{Symbol, Any}}, 1}
end

# Read the YOLO binary weights
function readweights(bytes::IOBuffer, kern::Int, ch::Int, fl::Int, bn::Bool)
    if bn
        bb = reinterpret(Float32, read(bytes, fl*4))
        bw = reinterpret(Float32, read(bytes, fl*4))
        bm = reinterpret(Float32, read(bytes, fl*4))
        bv = reinterpret(Float32, read(bytes, fl*4))
        cb = zeros(Float32, fl)
        cw = reshape(reinterpret(Float32, read(bytes, kern*kern*ch*fl*4)), kern, kern, ch, fl)
        cw = Float32.(flip(cw))
        return cw, cb, bb, bw, bm, bv
    else
        cb = reinterpret(Float32, read(bytes, fl*4))
        cw = reshape(reinterpret(Float32, read(bytes, kern*kern*ch*fl*4)), kern, kern, ch, fl)
        cw = Float32.(flip(cw))
        return cw, cb, 0.0, 0.0, 0.0, 0.0
    end
end

# Use different generators depending on presence of
onegen = in(:CuArrays, names(Main, imported = true)) ? CuArrays.ones : ones
zerogen = in(:CuArrays, names(Main, imported = true)) ? CuArrays.zeros : zeros

# YOLO wants a leakyrelu with a fixed leakyness of 0.1 so we define our own
leaky(x, a = oftype(x/1, 0.1)) = max(a*x, x/1)

# Provide an array of strings and an array of colors
# so the constructor can print what it's doing as it generates the model.
prettyprint(str, col) = for (s, c) in zip(str, col) printstyled(s, color=c) end

# Flip weights to make crosscorelation kernels work using convolution filters
# This is only run once when wheights are loaded
flip(x) = x[end:-1:1, end:-1:1, :, :]

# We need a max-pool with a fixed stride of 1
function maxpools1(x, kernel = 2)
    x = cat(x, x[:, end:end, :, :], dims = 2)
    x = cat(x, x[end:end, :, :, :], dims = 1)
    return maxpool(x, (kernel, kernel), stride = 1)
end

# Optimized upsampling without indexing for better  performance
function upsample(a, stride)
    m1, n1, o1, p1 = size(a)
    ar = reshape(a, (1, m1, 1, n1, o1, p1))
    b = onegen(stride, 1, stride, 1, 1, 1)
    return reshape(ar .* b, (m1 * stride, n1 * stride, o1, p1))
end

# Use this dict to translate the config activation names to function names
const ACT = Dict(
"leaky" => leaky,
"linear" => identity
)

mutable struct Yolo
    cfg::Dict{Symbol, Any}                   # This holds all settings for the model
    chain::Array{Any, 1}                     # This holds chains of weights and functions
    W::Dict{Int64, T} where T <: DenseArray  # This holds arrays that the model writes to
    out::Array{Dict{Symbol, Any}, 1}         # This holds values and arrays needed for inference
end


function gen_yolo(cfgfile::String, weightfile::String, batchsize::Int = 1)
    # read the config file and return [:layername => Dict(:setting => value), ...]
    # the first 'layer' is not a real layer, and has overarching YOLO settings
    cfgvec = cfgread(cfgfile)
    cfg = cfgvec[1][2]
    weightbytes = IOBuffer(read(weightfile)) # read weights file sequentially like byte stream
    # these settings are populated as the network is constructed below
    # some settings are re-read later for the last part of construction
    maj, min, subv, im1, im2 = reinterpret(Int32, read(weightbytes, 4*5))
    cfg[:version] = VersionNumber("$maj.$min.$subv")
    cfg[:batchsize] = batchsize
    cfg[:output] = []

    # PART 1 - THE LAYERS
    #####################
    ch = [cfg[:channels]] # this keeps track of channels per layer for creating convolutions
    fn = Array{Any, 1}(nothing, 0) # this keeps the 'function' generated by every layer
    for (blocktype, block) in cfgvec[2:end]
        if blocktype == :convolutional
            stack   = []
            kern    = block[:size]
            filters = block[:filters]
            pad     = Bool(block[:pad]) ? div(kern-1, 2) : 0
            stride  = block[:stride]
            act     = ACT[block[:activation]]
            bn      = haskey(block, :batch_normalize)
            cw, cb, bb, bw, bm, bv = readweights(weightbytes, kern, ch[end], filters, bn)
            push!(stack, (Conv(cw, cb; stride = stride, pad = pad, dilation = 1)))
            bn && push!(stack, (BatchNorm(identity, Flux.param(bb), Flux.param(bw), bm, bv, 1e-5, 0.1, false)))
            push!(stack, x -> act.(x))
        end
    end
end
Juno.@enter gen_yolo("yolov3.cfg","yolov3.weights",1)

For example I can paste into the REPL the variable cfgvec and I can see its contents until I step into the for loop. Inside the for loop I get UndefRefError error if I set breakpoint at kern = block[:size].
If I advance to push!(stack, (Conv(cw, cb; stride = stride, pad = pad, dilation = 1))) I can use the variable again in the REPL. This strange behavior happens at every iteration.

My questions are: did someone ever encounter such cases and what is special about the code above that prevents accessing local variables in the REPL in debug mode? (simple test function with for loop doesn’t have this issue)

  • using Julia 1.1.1 on Windows
1 Like

I’ve started encountering the same thing. I was working on a function and it was debugging just fine, and then it started doing something like this. Beyond a certain line it is unable to resolve, though it appears to step through correctly otherwise.

May or may not be related, but I notice that at this point a variable with a type of “Core.Box” is assigned.

Encountered the same issue when using ImageSegmentation:

function segm_img1(img,p)
        labels = []
        segments = fast_scanning(img, p)
        segments = prune_segments(segments, i->(segment_pixel_count(segments,i)<50),
        (i,j)->(-segment_pixel_count(segments,j)))
end

Once I advance past the first line in debugger, the variable “labels”, for example, is undefined.
If I comment the “prune_segments” function, all variables are again visible.

1 Like