Yalantis/uCrop · error · CImgArgumentException

[" cimg_appname "_math_parser] CImg<%s>::%s: Unterminated st

Error message

[" cimg_appname "_math_parser] CImg<%s>::%s: Unterminated string literal, in expression '%s'.

What it means

CImg's math expression parser (used by functions like fill() with an expression string, or expression-based image operations) scans the expression for string literals delimited by quotes. If the parser reaches the end of input while still inside a quoted string (mode != 0), it throws CImgArgumentException reporting an unterminated string literal, truncating the expression to 64 chars via strellipsize for readability.

Source

Thrown at ucrop/src/main/jni/CImg.h:25016

        int _level = 0;
        for (const char *ps = _expr._data; *ps && _level>=0; ++ps) {
          if (!is_escaped && !next_is_escaped && *ps=='\\') next_is_escaped = true;
          if (!is_escaped && *ps=='\'') { // Non-escaped character
            if (!mode && ps>_expr._data && *(ps - 1)=='[') next_mode = mode = 2; // Start vector-string
            else if (mode==2 && *(ps + 1)==']') next_mode = !mode; // End vector-string
            else if (mode<2) next_mode = mode?(mode = 0):1; // Start/end char-string
          }
          *(pd++) = (unsigned int)(mode>=1 || is_escaped?_level + (mode==1):
                                   *ps=='(' || *ps=='['?_level++:
                                   *ps==')' || *ps==']'?--_level:
                                   _level);
          mode = next_mode;
          is_escaped = next_is_escaped;
          next_is_escaped = false;
        }
        if (mode) {
          cimg::strellipsize(_expr,64);
          throw CImgArgumentException("[" cimg_appname "_math_parser] "
                                      "CImg<%s>::%s: Unterminated string literal, in expression '%s'.",
                                      pixel_type(),_cimg_mp_calling_function,
                                      _expr._data);
        }
        if (_level) {
          cimg::strellipsize(_expr,64);
          throw CImgArgumentException("[" cimg_appname "_math_parser] "
                                      "CImg<%s>::%s: Unbalanced parentheses/brackets, in expression '%s'.",
                                      pixel_type(),_cimg_mp_calling_function,
                                      _expr._data);
        }
        return res;
      }

      // Find and return index of current image 'imgin' within image list 'imglist'.
      unsigned int get_mem_img_index() {
        if (mem_img_index==~0U) {
          if (&imgout>=imglist.data() && &imgout<imglist.end())

View on GitHub (pinned to f788b534b4)

Solutions

  1. Inspect the expression string at runtime (log it) and find the opening quote that is never closed
  2. Ensure every string literal inside the expression has a matching closing quote of the same character
  3. If the expression passes through a shell, JSON, or XML layer, verify quotes survive escaping (double them or use the layer's escape syntax)
  4. Split the expression into smaller pieces and test each in isolation to locate the malformed literal

Example fix

// before
img.fill("if(x>2,'odd',0)"); // fine
img.fill("if(x>2,'odd,0)");  // unterminated string literal
// after
img.fill("if(x>2,'odd',0)");
Defensive patterns

Strategy: validation

Validate before calling

// C++: check quote balance before passing expression to CImg
bool quotes_balanced(const std::string& e) {
  int sq = 0, dq = 0;
  for (size_t i = 0; i < e.size(); ++i) {
    if (i > 0 && e[i-1] == '\\') continue; // skip escaped
    if (e[i] == '\'') sq ^= 1;
    if (e[i] == '"') dq ^= 1;
  }
  return sq == 0 && dq == 0;
}
if (!quotes_balanced(expr)) throw std::invalid_argument("unterminated string literal in expression");

Type guard

bool has_balanced_quotes(const std::string& e); // see validationCode

Try / catch

try {
  img.fill(expr);
} catch (const cimg_library::CImgArgumentException& e) {
  std::cerr << "Bad expression: " << e.what() << " | expr=" << expr << std::endl;
}

Prevention

When it happens

Trigger: Passing a math expression string to a CImg function (e.g. img.fill("...") or an expression in a formula-based call) where an opening quote (' or ") is never closed, e.g. fill("if(x>2,'hello',0)") with a missing closing quote, or a quote accidentally escaped/eaten by an outer shell or JSON layer.

Common situations: Building expressions programmatically or in shell scripts where quotes get stripped by the shell or a JSON/ini config layer; typos in G'MIC-style expressions; copy-pasting expressions where smart quotes or one of a matched pair is lost.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of Yalantis/uCrop@f788b534b4 (2026-09-08). Data as JSON: /api/errors/b9f8260ff1ab0e87. Report an issue: GitHub.