plugin types BUGFIX ::ly_err_new() format emphasis

This commit prevents an error where err_msg could contain a conversion
specifier (character '%'), but the variadic arguments are empty, which
causes undefined behavior. Therefore, in the function, the err_msg
parameter has been renamed to err_format to emphasize that the function
expects a format. A compiler attribute has also been added to the
function to check the correct number of variadic arguments.
diff --git a/src/config.h.in b/src/config.h.in
index 65c4934..a6aff60 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -26,4 +26,10 @@
 #define LYPLG_TYPE_DIR "@PLUGINS_DIR_EXTENSIONS@"
 #define LYPLG_EXT_DIR "@PLUGINS_DIR_TYPES@"
 
+#if (@CMAKE_C_COMPILER_ID@ == GNU) || (@CMAKE_C_COMPILER_ID@ == Clang)
+# define _FORMAT_PRINTF(FORM, ARGS) __attribute__((format (printf, FORM, ARGS)))
+#else
+# define _FORMAT_PRINTF(FORM, ARGS)
+#endif
+
 #endif /* LY_CONFIG_H_ */
diff --git a/src/log.c b/src/log.c
index 9cd5fd0..f38772b 100644
--- a/src/log.c
+++ b/src/log.c
@@ -118,7 +118,7 @@
 }
 
 API LY_ERR
-ly_err_new(struct ly_err_item **err, LY_ERR ecode, LY_VECODE vecode, char *path, char *apptag, const char *err_msg, ...)
+ly_err_new(struct ly_err_item **err, LY_ERR ecode, LY_VECODE vecode, char *path, char *apptag, const char *err_format, ...)
 {
     char *msg = NULL;
     struct ly_err_item *e;
@@ -143,13 +143,13 @@
     e->path = path;
     e->apptag = apptag;
 
-    if (err_msg) {
+    if (err_format) {
         va_list print_args;
 
-        va_start(print_args, err_msg);
+        va_start(print_args, err_format);
 
-        if (vasprintf(&msg, err_msg, print_args) == -1) {
-            /* we don't have anything more to do, just set err_msg to NULL to avoid undefined content,
+        if (vasprintf(&msg, err_format, print_args) == -1) {
+            /* we don't have anything more to do, just set msg to NULL to avoid undefined content,
              * still keep the information about the original error instead of LY_EMEM or other printf's error */
             msg = NULL;
         }
diff --git a/src/plugins_types.c b/src/plugins_types.c
index 24dec93..bd59b9a 100644
--- a/src/plugins_types.c
+++ b/src/plugins_types.c
@@ -490,13 +490,13 @@
             PCRE2_UCHAR pcre2_errmsg[LY_PCRE2_MSG_LIMIT] = {0};
             pcre2_get_error_message(rc, pcre2_errmsg, LY_PCRE2_MSG_LIMIT);
 
-            return ly_err_new(err, LY_ESYS, 0, NULL, NULL, (const char *)pcre2_errmsg);
+            return ly_err_new(err, LY_ESYS, 0, NULL, NULL, "%s", (const char *)pcre2_errmsg);
         } else if (((rc == PCRE2_ERROR_NOMATCH) && !patterns[u]->inverted) ||
                 ((rc != PCRE2_ERROR_NOMATCH) && patterns[u]->inverted)) {
             char *eapptag = patterns[u]->eapptag ? strdup(patterns[u]->eapptag) : NULL;
 
             if (patterns[u]->emsg) {
-                return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, patterns[u]->emsg);
+                return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, "%s", patterns[u]->emsg);
             } else {
                 const char *inverted = patterns[u]->inverted ? "inverted " : "";
                 return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag,
@@ -523,7 +523,7 @@
             if ((uint64_t)value < range->parts[u].min_u64) {
                 char *eapptag = range->eapptag ? strdup(range->eapptag) : NULL;
                 if (range->emsg) {
-                    return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, range->emsg);
+                    return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, "%s", range->emsg);
                 } else {
                     return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag,
                             is_length ? LY_ERRMSG_NOLENGTH : LY_ERRMSG_NORANGE, (int)strval_len, strval);
@@ -535,7 +535,7 @@
                 /* we have the last range part, so the value is out of bounds */
                 char *eapptag = range->eapptag ? strdup(range->eapptag) : NULL;
                 if (range->emsg) {
-                    return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, range->emsg);
+                    return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, "%s", range->emsg);
                 } else {
                     return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag,
                             is_length ? LY_ERRMSG_NOLENGTH : LY_ERRMSG_NORANGE, (int)strval_len, strval);
@@ -546,7 +546,7 @@
             if (value < range->parts[u].min_64) {
                 char *eapptag = range->eapptag ? strdup(range->eapptag) : NULL;
                 if (range->emsg) {
-                    return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, range->emsg);
+                    return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, "%s", range->emsg);
                 } else {
                     return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, LY_ERRMSG_NORANGE, (int)strval_len, strval);
                 }
@@ -557,7 +557,7 @@
                 /* we have the last range part, so the value is out of bounds */
                 char *eapptag = range->eapptag ? strdup(range->eapptag) : NULL;
                 if (range->emsg) {
-                    return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, range->emsg);
+                    return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, "%s", range->emsg);
                 } else {
                     return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, LY_ERRMSG_NORANGE, (int)strval_len, strval);
                 }
diff --git a/src/plugins_types.h b/src/plugins_types.h
index c074f3b..1763134 100644
--- a/src/plugins_types.h
+++ b/src/plugins_types.h
@@ -213,13 +213,16 @@
  * @param[in] vecode Validity error code in case of LY_EVALID error code.
  * @param[in] path Path to the node causing the error.
  * @param[in] apptag Error-app-tag value.
- * @param[in] err_msg error formating message.
+ * @param[in] err_format Format string (same like at printf) or string literal.
+ * If you want to print just an unknown string, use "%s" for the @p err_format, otherwise undefined behavior may occur
+ * because the unknown string may contain the % character, which is interpreted as conversion specifier.
  * @return The given @p ecode value if the @p err is successfully created. The structure can be freed using ::ly_err_free()
  * or passed back from callback into libyang.
  * @return LY_EMEM If there is not enough memory for allocating error record, the @p err is not touched in that case.
  * @return LY_SUCCESS if @p ecode is LY_SUCCESS, the @p err is not touched in this case.
  */
-LY_ERR ly_err_new(struct ly_err_item **err, LY_ERR ecode, LY_VECODE vecode, char *path, char *apptag, const char *err_msg, ...);
+LY_ERR ly_err_new(struct ly_err_item **err, LY_ERR ecode, LY_VECODE vecode, char *path, char *apptag, const char *err_format, ...)
+_FORMAT_PRINTF(6, 7);
 
 /**
  * @brief Destructor for the error records created with ::ly_err_new().
diff --git a/src/plugins_types/leafref.c b/src/plugins_types/leafref.c
index ab5842a..19e2029 100644
--- a/src/plugins_types/leafref.c
+++ b/src/plugins_types/leafref.c
@@ -79,7 +79,7 @@
 
     /* check leafref target existence */
     if (lyplg_type_resolve_leafref(type_lr, ctx_node, storage, tree, NULL, &errmsg)) {
-        ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, errmsg);
+        ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "%s", errmsg);
         free(errmsg);
         return ret;
     }
diff --git a/src/plugins_types/union.c b/src/plugins_types/union.c
index 89e81c7..e756a1b 100644
--- a/src/plugins_types/union.c
+++ b/src/plugins_types/union.c
@@ -167,7 +167,7 @@
         type_idx = *(uint32_t *)value;
         if (type_idx >= LY_ARRAY_COUNT(type_u->types)) {
             ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB union type index %" PRIu32
-                    " (type count " LY_PRI_ARRAY_COUNT_TYPE ").", type_idx, LY_ARRAY_COUNT(type_u->types));
+                    " (type count %" LY_PRI_ARRAY_COUNT_TYPE ").", type_idx, LY_ARRAY_COUNT(type_u->types));
             goto cleanup;
         }
     }
diff --git a/tests/utests/data/test_types.c b/tests/utests/data/test_types.c
index cddda2a..3944486 100644
--- a/tests/utests/data/test_types.c
+++ b/tests/utests/data/test_types.c
@@ -952,6 +952,9 @@
     CHECK_LOG_CTX("Invalid leafref value \"z\" - no existing target instance \"../../l[id=current()/../../../t:str-norestr]"
             "[value=current()/../../../t:str-norestr]/value\".",
             "Schema location /leafrefs:c/l/lr2, data location /leafrefs:c/l[id='x'][value='x']/lr2.");
+
+    data = "<lref xmlns=\"urn:tests:types\">%n</lref>";
+    CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_EVALID, tree);
 }
 
 static void