Skip to content

Catalina problems

bgoodri edited this page Nov 7, 2019 · 10 revisions

Introduction

Catalina does not seem to handle exceptions properly. This does not seem to be a problem with Stan code, but affects Stan adversely because

  • Stan has many different types of exceptions
  • Stan behaves differently depending on which type of exception is thrown
  • The occurrence of an exception during MCMC, ADVI, or optimization is probabilistic but likely, even in well-written Stan programs

This wiki page attempts to document what works as what does not work.

Test Program

Rcpp::sourceCpp(code = 
'
#include <Rcpp.h>

// [[Rcpp::export]]
int throw_exception() { 
  std::stringstream errmsg; errmsg << "this is the expected behavior";
  throw std::domain_error(errmsg.str()); 
  return 0;
}
'
)

throw_exception()

Results of Test Program

Successful

The throw_exception() function returns "this is the expected behavior" on

  • Linux, regardless of whether clang++ or g++ is used to compile
  • Windows, using RTools35
  • Mac (Mojave operating system) with clang++ from CRAN via coatless' installer
  • Mac (Catalina operating system) with clang++ from XCode, provided that Rcpp is compiled from source with clang++ from XCode and no binary packages with C++ code from CRAN (readr, ggplot2, etc.) have been loaded
  • Mac (Catalina operating system), with R installed via homebrew and clang++ from XCode
  • Mac (Catalina operating system) with R from CRAN but g++ installed from homebrew, but this may require that you install Rcpp from source with g++.

Unsuccessful

The throw_exception() function returns "c++ exception (unknown reason)" on

  • Mac (Catalina operating system) with clang++ from CRAN via coatless' installer
  • Mac (Catalina operating system) with clang++ from XCode if any binary package with C++ code from CRAN (readr, ggplot2, etc.) has been loaded

Code to verify what C++ library was used

Kevin Ushey wrote the following to show which C++ library was used on a Mac

dlls <- getLoadedDLLs()
paths <- vapply(dlls, `[[`, "path", FUN.VALUE = character(1))
invisible(lapply(paths, function(path) {
  
  if (!file.exists(path))
    return(FALSE)
  
  output <- system(paste("otool -L", shQuote(path), "| grep libc++ || true"), intern = TRUE)
  if (length(output) == 0)
    return(FALSE)
  
  writeLines(paste0(path, ":"))
  writeLines(output)
  
}))

For each dynamically loaded library, tt will say something like

/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 800.6.0)

or

/Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1.0.0)

Potential solutions

install_name_tool

When we first encountered this problem back in 2017

https://github.com/jeroen/V8/issues/37#issuecomment-305266135

we were able to fix it with a suggested hack by Kevin Ushey, but the function below seems not to fix the problem on Catalina:

fix_Mac <- function(sm) {
  stopifnot(is(sm, "stanmodel"))
  dso_last_path <- sm@[email protected]$dso_last_path
  CLANG_DIR <- tail(n = 1, grep("clang[456789]", 
                    x = list.dirs("/usr/local", recursive = FALSE), 
                    value = TRUE))
  if (length(CLANG_DIR) == 0L) stop("no clang from CRAN found")
  LIBCPP <- file.path(CLANG_DIR, "lib", "libc++.1.dylib")
  if (!file.exists(LIBCPP)) stop("no unique libc++.1?.dylib found")
  Rv <- R.version
  GOOD <- file.path("/Library", "Frameworks", "R.framework", "Versions", 
                     paste(Rv$major, substr(Rv$minor, 1, 1), sep = "."), 
                     "Resources", "lib", "libc++.1.dylib")
  if (!file.exists(GOOD)) stop(paste(GOOD, "not found"))
  cmd <- paste(
    "install_name_tool",
    "-change",
    LIBCPP,
    GOOD,
    dso_last_path
  )
  system(cmd)
  dyn.unload(dso_last_path)
  dyn.load(dso_last_path)
  return(invisible(NULL))
}

Avoiding the system C++ libraries

According to https://libcxx.llvm.org/docs/UsingLibcxx.html , the system version of libc++ can be avoided with something like

clang++ -std=c++11 -stdlib=libc++ -nostdinc++ \
          -I<libcxx-install-prefix>/include/c++/v1 \
          -L<libcxx-install-prefix>/lib \
          -Wl,-rpath,<libcxx-install-prefix>/lib \
          test.cpp

where is something like /usr/local/clang[456789]/ . When putting something like that into src/Makevars of a package, it is possible to compile correctly but the linker still uses /usr/lib/libc++.1.dylib even when the environmental variable DYLD_LIBRARY_PATH is set to /usr/local/clang[456789]/lib .

Statically linking

No one has tried this yet, and it is discouraged by Apple in some cases, but it may be possible to statically link the correct version of a C++ library.