Skip to content

Instantly share code, notes, and snippets.

@MichaelChirico
Created December 16, 2025 20:19
Show Gist options
  • Select an option

  • Save MichaelChirico/fb697a2be62f4c7b9cffbc2316494b2e to your computer and use it in GitHub Desktop.

Select an option

Save MichaelChirico/fb697a2be62f4c7b9cffbc2316494b2e to your computer and use it in GitHub Desktop.
units crashes if udunits compiled with -fno-exceptions
#!/bin/bash
set -e # Exit immediately if a command exits with a non-zero status.
# --- 1. Environment Setup ---
echo ">>> Installing System Dependencies..."
sudo apt-get update -qq
# Dependencies:
# - flex/bison: for udunits parser compilation
# - texinfo: for udunits documentation
# - libexpat1-dev: for udunits XML parsing
sudo apt-get install -y -qq r-base-dev cmake libexpat1-dev autoconf automake texinfo flex bison
# Create a local R library directory to avoid using 'sudo' later
mkdir -p ~/R_libs
export R_LIBS_USER=~/R_libs
echo "R_LIBS_USER=~/R_libs" > .Renviron
# Install Rcpp into the local library
echo ">>> Installing Rcpp..."
R -e "install.packages('Rcpp', repos='https://cloud.r-project.org', lib='~/R_libs')"
# --- 2. Build 'Broken' udunits (No Exceptions) ---
echo ">>> Cloning and Building udunits (Blocking Exceptions)..."
# Clean up previous build completely
if [ -d "udunits-2" ]; then
sudo rm -rf udunits-2
fi
git clone https://github.com/unidata/udunits-2.git
cd udunits-2
mkdir build && cd build
# Compile with flags that explicitly disable exception handling tables
cmake .. \
-DCMAKE_INSTALL_PREFIX=$HOME/udunits-broken \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_FLAGS="-O2 -g0 -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -fomit-frame-pointer"
make install > /dev/null
cd ../..
# Export paths so the system knows where to find our custom library
export UDUNITS_ROOT=$HOME/udunits-broken
export LD_LIBRARY_PATH=$UDUNITS_ROOT/lib:$LD_LIBRARY_PATH
# --- 3. Reproduce the Crash (Before Fix) ---
echo ">>> Cloning 'units' package..."
# Remove existing repo with sudo to delete any root-owned artifacts from previous runs
if [ -d "units" ]; then
sudo rm -rf units
fi
git clone https://github.com/r-quantities/units.git
cd units
echo ">>> Installing 'units' against broken library..."
# NOTE: We do NOT use sudo here. We install to the local ~/R_libs.
R CMD INSTALL . --configure-args="--with-udunits2-include=$UDUNITS_ROOT/include --with-udunits2-lib=$UDUNITS_ROOT/lib"
echo ">>> DEMONSTRATION: Running R script to trigger crash..."
echo " (If successful, this will crash with 'Aborted' or 'terminate called')"
set +e # Allow the next command to fail (crash)
# Using the specific snippet that triggers the hard crash
R -e "library(units); print('Attempting invalid conversion...'); ud_convert(100, 'm', 'kg')"
EXIT_CODE=$?
set -e
if [ $EXIT_CODE -ne 0 ]; then
echo -e "\n\033[0;32m>>> CRASH CONFIRMED: R process terminated unexpectedly (Exit Code: $EXIT_CODE).\033[0m"
echo " This proves the current units package is unsafe with this library."
else
echo -e "\n\033[0;31m>>> UNEXPECTED: R did not crash. The library might support exceptions?\033[0m"
fi
# --- 4. Apply the Fix ---
echo ">>> Applying patch to configure.ac..."
# Inject the check logic into configure.ac
sed -i '/UD_ERROR="libudunits2.so was not found")/a \
\
if test -z "${UD_ERROR}"; then\
AC_MSG_CHECKING([whether udunits2 accepts exceptions])\
AC_RUN_IFELSE([AC_LANG_PROGRAM(\
[[\
#include <stdexcept>\
#include <cstdarg>\
#ifdef HAVE_UDUNITS2_UDUNITS2_H\
# include <udunits2/udunits2.h>\
#else\
# include <udunits2.h>\
#endif\
int my_handler(const char* fmt, va_list args) {\
throw std::runtime_error("udunits error");\
return 0;\
}\
]],\
[[\
ut_set_error_message_handler(my_handler);\
ut_system* system = ut_read_xml(NULL);\
if (system) ut_parse(system, "ThisIsNotAUnit", UT_ASCII);\
return 1; \
]])],\
[AC_MSG_RESULT([yes])],\
[AC_MSG_RESULT([no]); AC_MSG_ERROR([udunits2 does not support exceptions. Recompile with -fexceptions.])],\
[AC_MSG_RESULT([unknown])]\
)\
fi' configure.ac
# --- 5. Verify the Fix ---
echo ">>> Regenerating configure script..."
autoconf
echo ">>> Running ./configure against broken library..."
# This configuration attempt should now FAIL because of our new check
if ./configure --with-udunits2-include=$UDUNITS_ROOT/include --with-udunits2-lib=$UDUNITS_ROOT/lib; then
echo ">>> FAILED: Configure passed, but it should have failed."
else
echo -e "\n\033[0;32m>>> SUCCESS: Configure failed as expected!\033[0m"
echo " The script correctly detected that udunits2 cannot propagate exceptions."
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment