Created
December 16, 2025 20:19
-
-
Save MichaelChirico/fb697a2be62f4c7b9cffbc2316494b2e to your computer and use it in GitHub Desktop.
units crashes if udunits compiled with -fno-exceptions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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