Commit b80ffc5b2ea for php.net

commit b80ffc5b2ead9a91327edebd2760c8f2b927ed38
Author: Juan Morales <jcmargentina@gmail.com>
Date:   Sat Jan 3 14:34:19 2026 -0300

    Json last error msg/error message with location error (#20629)

    This slightly extend error messages with locations taken from scanner / parser

diff --git a/ext/json/json.c b/ext/json/json.c
index 9f91d39594e..079f67a5c40 100644
--- a/ext/json/json.c
+++ b/ext/json/json.c
@@ -63,6 +63,8 @@ static PHP_GINIT_FUNCTION(json)
 #endif
 	json_globals->encoder_depth = 0;
 	json_globals->error_code = 0;
+	json_globals->error_line = 0;
+	json_globals->error_column = 0;
 	json_globals->encode_max_depth = PHP_JSON_PARSER_DEFAULT_DEPTH;
 }
 /* }}} */
@@ -70,6 +72,8 @@ static PHP_GINIT_FUNCTION(json)
 static PHP_RINIT_FUNCTION(json)
 {
 	JSON_G(error_code) = 0;
+	JSON_G(error_line) = 0;
+	JSON_G(error_column) = 0;
 	return SUCCESS;
 }

@@ -177,6 +181,18 @@ static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{
 }
 /* }}} */

+static zend_string *php_json_get_error_msg_with_location(php_json_error_code error_code, size_t line, size_t column) /* {{{ */
+{
+	const char *base_msg = php_json_get_error_msg(error_code);
+
+	if (line > 0 && column > 0) {
+		return zend_strpprintf(0, "%s near location %zu:%zu", base_msg, line, column);
+	}
+
+	return zend_string_init(base_msg, strlen(base_msg), 0);
+}
+/* }}} */
+
 PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long depth) /* {{{ */
 {
 	php_json_parser parser;
@@ -185,10 +201,17 @@ PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str,

 	if (php_json_yyparse(&parser)) {
 		php_json_error_code error_code = php_json_parser_error_code(&parser);
+		size_t error_line = php_json_parser_error_line(&parser);
+		size_t error_column = php_json_parser_error_column(&parser);
+
 		if (!(options & PHP_JSON_THROW_ON_ERROR)) {
 			JSON_G(error_code) = error_code;
+			JSON_G(error_line) = error_line;
+			JSON_G(error_column) = error_column;
 		} else {
-			zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(error_code), error_code);
+			zend_string *error_msg = php_json_get_error_msg_with_location(error_code, error_line, error_column);
+			zend_throw_exception(php_json_exception_ce, ZSTR_VAL(error_msg), error_code);
+			zend_string_release(error_msg);
 		}
 		RETVAL_NULL();
 		return FAILURE;
@@ -208,7 +231,12 @@ PHP_JSON_API bool php_json_validate_ex(const char *str, size_t str_len, zend_lon

 	if (php_json_yyparse(&parser)) {
 		php_json_error_code error_code = php_json_parser_error_code(&parser);
+		size_t error_line = php_json_parser_error_line(&parser);
+		size_t error_column = php_json_parser_error_column(&parser);
+
 		JSON_G(error_code) = error_code;
+		JSON_G(error_line) = error_line;
+		JSON_G(error_column) = error_column;
 		return false;
 	}

@@ -274,11 +302,15 @@ PHP_FUNCTION(json_decode)

 	if (!(options & PHP_JSON_THROW_ON_ERROR)) {
 		JSON_G(error_code) = PHP_JSON_ERROR_NONE;
+		JSON_G(error_line) = 0;
+		JSON_G(error_column) = 0;
 	}

 	if (!str_len) {
 		if (!(options & PHP_JSON_THROW_ON_ERROR)) {
 			JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX;
+			JSON_G(error_line) = 0;
+			JSON_G(error_column) = 0;
 		} else {
 			zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(PHP_JSON_ERROR_SYNTAX), PHP_JSON_ERROR_SYNTAX);
 		}
@@ -331,10 +363,14 @@ PHP_FUNCTION(json_validate)

 	if (!str_len) {
 		JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX;
+		JSON_G(error_line) = 0;
+		JSON_G(error_column) = 0;
 		RETURN_FALSE;
 	}

 	JSON_G(error_code) = PHP_JSON_ERROR_NONE;
+	JSON_G(error_line) = 0;
+	JSON_G(error_column) = 0;

 	if (depth <= 0) {
 		zend_argument_value_error(2, "must be greater than 0");
@@ -364,6 +400,10 @@ PHP_FUNCTION(json_last_error_msg)
 {
 	ZEND_PARSE_PARAMETERS_NONE();

-	RETURN_STRING(php_json_get_error_msg(JSON_G(error_code)));
+	RETVAL_STR(php_json_get_error_msg_with_location(
+		JSON_G(error_code),
+		JSON_G(error_line),
+		JSON_G(error_column)
+	));
 }
 /* }}} */
diff --git a/ext/json/json_parser.y b/ext/json/json_parser.y
index d570cddc91e..2fd4edfe369 100644
--- a/ext/json/json_parser.y
+++ b/ext/json/json_parser.y
@@ -41,6 +41,7 @@ int json_yydebug = 1;

 }

+%locations
 %define api.prefix {php_json_yy}
 %define api.pure full
 %param  { php_json_parser *parser  }
@@ -49,7 +50,6 @@ int json_yydebug = 1;
 	zval value;
 }

-
 %token <value> PHP_JSON_T_NUL
 %token <value> PHP_JSON_T_TRUE
 %token <value> PHP_JSON_T_FALSE
@@ -66,8 +66,8 @@ int json_yydebug = 1;
 %destructor { zval_ptr_dtor_nogc(&$$); } <value>

 %code {
-static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser);
-static void php_json_yyerror(php_json_parser *parser, char const *msg);
+static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_parser *parser);
+static void php_json_yyerror(YYLTYPE *location, php_json_parser *parser, char const *msg);
 static int php_json_parser_array_create(php_json_parser *parser, zval *array);
 static int php_json_parser_object_create(php_json_parser *parser, zval *array);

@@ -277,7 +277,7 @@ static int php_json_parser_object_update_validate(php_json_parser *parser, zval
 	return SUCCESS;
 }

-static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser)
+static int php_json_yylex(union YYSTYPE *value, YYLTYPE *location, php_json_parser *parser)
 {
 	int token = php_json_scan(&parser->scanner);

@@ -293,10 +293,15 @@ static int php_json_yylex(union YYSTYPE *value, php_json_parser *parser)
 		value->value = parser->scanner.value;
 	}

+	location->first_column = PHP_JSON_SCANNER_LOCATION(parser->scanner, first_column);
+	location->first_line = PHP_JSON_SCANNER_LOCATION(parser->scanner, first_line);
+	location->last_column = PHP_JSON_SCANNER_LOCATION(parser->scanner, last_column);
+	location->last_line = PHP_JSON_SCANNER_LOCATION(parser->scanner, last_line);
+
 	return token;
 }

-static void php_json_yyerror(php_json_parser *parser, char const *msg)
+static void php_json_yyerror(YYLTYPE *location, php_json_parser *parser, char const *msg)
 {
 	if (!parser->scanner.errcode) {
 		parser->scanner.errcode = PHP_JSON_ERROR_SYNTAX;
@@ -308,6 +313,16 @@ PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parse
 	return parser->scanner.errcode;
 }

+PHP_JSON_API size_t php_json_parser_error_line(const php_json_parser *parser)
+{
+	return parser->scanner.errloc.first_line;
+}
+
+PHP_JSON_API size_t php_json_parser_error_column(const php_json_parser *parser)
+{
+	return parser->scanner.errloc.first_column;
+}
+
 static const php_json_parser_methods default_parser_methods =
 {
 	php_json_parser_array_create,
diff --git a/ext/json/json_scanner.re b/ext/json/json_scanner.re
index 0debb3b03cb..d6eaaf65b2e 100644
--- a/ext/json/json_scanner.re
+++ b/ext/json/json_scanner.re
@@ -52,6 +52,8 @@

 #define PHP_JSON_INT_MAX_LENGTH (MAX_LENGTH_OF_LONG - 1)

+#define PHP_JSON_TOKEN_LENGTH() ((size_t) (s->cursor - s->token))
+#define PHP_JSON_TOKEN_LOCATION(location) (s)->errloc.location

 static void php_json_scanner_copy_string(php_json_scanner *s, size_t esc_size)
 {
@@ -96,6 +98,10 @@ void php_json_scanner_init(php_json_scanner *s, const char *str, size_t str_len,
 	s->cursor = (php_json_ctype *) str;
 	s->limit = (php_json_ctype *) str + str_len;
 	s->options = options;
+	PHP_JSON_TOKEN_LOCATION(first_column) = 1;
+	PHP_JSON_TOKEN_LOCATION(first_line) = 1;
+	PHP_JSON_TOKEN_LOCATION(last_column) = 1;
+	PHP_JSON_TOKEN_LOCATION(last_line) = 1;
 	PHP_JSON_CONDITION_SET(JS);
 }

@@ -104,6 +110,8 @@ int php_json_scan(php_json_scanner *s)
 	ZVAL_NULL(&s->value);

 std:
+	PHP_JSON_TOKEN_LOCATION(first_column) = s->errloc.last_column;
+	PHP_JSON_TOKEN_LOCATION(first_line) = s->errloc.last_line;
 	s->token = s->cursor;

 /*!re2c
@@ -149,27 +157,50 @@ std:
 	UTF16_3 = UTFPREF ( ( ( HEXC | [efEF] ) HEX ) | ( [dD] HEX7 ) ) HEX{2} ;
 	UTF16_4 = UTFPREF [dD] [89abAB] HEX{2} UTFPREF [dD] [c-fC-F] HEX{2} ;

-	<JS>"{"                  { return '{'; }
-	<JS>"}"                  { return '}'; }
-	<JS>"["                  { return '['; }
-	<JS>"]"                  { return ']'; }
-	<JS>":"                  { return ':'; }
-	<JS>","                  { return ','; }
+	<JS>"{"                  {
+		PHP_JSON_TOKEN_LOCATION(last_column)++;
+		return '{';
+	}
+	<JS>"}"                  {
+		PHP_JSON_TOKEN_LOCATION(last_column)++;
+		return '}';
+	}
+	<JS>"["                  {
+		PHP_JSON_TOKEN_LOCATION(last_column)++;
+		return '[';
+	}
+	<JS>"]"                  {
+		PHP_JSON_TOKEN_LOCATION(last_column)++;
+		return ']';
+	}
+	<JS>":"                  {
+		PHP_JSON_TOKEN_LOCATION(last_column)++;
+		return ':';
+	}
+	<JS>","                  {
+		PHP_JSON_TOKEN_LOCATION(last_column)++;
+		return ',';
+	}
 	<JS>"null"               {
+		PHP_JSON_TOKEN_LOCATION(last_column) += 4;
 		ZVAL_NULL(&s->value);
 		return PHP_JSON_T_NUL;
 	}
 	<JS>"true"               {
+		PHP_JSON_TOKEN_LOCATION(last_column) += 4;
 		ZVAL_TRUE(&s->value);
 		return PHP_JSON_T_TRUE;
 	}
 	<JS>"false"              {
+		PHP_JSON_TOKEN_LOCATION(last_column) += 5;
 		ZVAL_FALSE(&s->value);
 		return PHP_JSON_T_FALSE;
 	}
 	<JS>INT                  {
 		bool bigint = 0, negative = s->token[0] == '-';
-		size_t digits = (size_t) (s->cursor - s->token - negative);
+		size_t digits = PHP_JSON_TOKEN_LENGTH();
+		PHP_JSON_TOKEN_LOCATION(last_column) += digits;
+		digits -= negative;
 		if (digits >= PHP_JSON_INT_MAX_LENGTH) {
 			if (digits == PHP_JSON_INT_MAX_LENGTH) {
 				int cmp = strncmp((char *) (s->token + negative), LONG_MIN_DIGITS, PHP_JSON_INT_MAX_LENGTH);
@@ -192,10 +223,19 @@ std:
 		}
 	}
 	<JS>FLOAT|EXP            {
+		PHP_JSON_TOKEN_LOCATION(last_column) += PHP_JSON_TOKEN_LENGTH();
 		ZVAL_DOUBLE(&s->value, zend_strtod((char *) s->token, NULL));
 		return PHP_JSON_T_DOUBLE;
 	}
-	<JS>NL|WS                { goto std; }
+	<JS>NL                   {
+		PHP_JSON_TOKEN_LOCATION(last_line)++;
+		PHP_JSON_TOKEN_LOCATION(last_column) = 1;
+		goto std;
+	}
+	<JS>WS                   {
+		PHP_JSON_TOKEN_LOCATION(last_column) += PHP_JSON_TOKEN_LENGTH();
+		goto std;
+	}
 	<JS>EOI                  {
 		if (s->limit < s->cursor) {
 			return PHP_JSON_T_EOI;
@@ -205,6 +245,7 @@ std:
 		}
 	}
 	<JS>["]                  {
+		PHP_JSON_TOKEN_LOCATION(last_column)++;
 		s->str_start = s->cursor;
 		s->str_esc = 0;
 		s->utf8_invalid = 0;
@@ -229,18 +270,22 @@ std:
 		return PHP_JSON_T_ERROR;
 	}
 	<STR_P1>UTF16_1          {
+		PHP_JSON_TOKEN_LOCATION(last_column) += 1;
 		s->str_esc += 5;
 		PHP_JSON_CONDITION_GOTO(STR_P1);
 	}
 	<STR_P1>UTF16_2          {
+		PHP_JSON_TOKEN_LOCATION(last_column) += 1;
 		s->str_esc += 4;
 		PHP_JSON_CONDITION_GOTO(STR_P1);
 	}
 	<STR_P1>UTF16_3          {
+		PHP_JSON_TOKEN_LOCATION(last_column) += 1;
 		s->str_esc += 3;
 		PHP_JSON_CONDITION_GOTO(STR_P1);
 	}
 	<STR_P1>UTF16_4          {
+		PHP_JSON_TOKEN_LOCATION(last_column) += 1;
 		s->str_esc += 8;
 		PHP_JSON_CONDITION_GOTO(STR_P1);
 	}
@@ -249,6 +294,7 @@ std:
 		return PHP_JSON_T_ERROR;
 	}
 	<STR_P1>ESC              {
+		PHP_JSON_TOKEN_LOCATION(last_column) += 2;
 		s->str_esc++;
 		PHP_JSON_CONDITION_GOTO(STR_P1);
 	}
@@ -257,6 +303,7 @@ std:
 		return PHP_JSON_T_ERROR;
 	}
 	<STR_P1>["]              {
+		PHP_JSON_TOKEN_LOCATION(last_column)++;
 		zend_string *str;
 		size_t len = (size_t)(s->cursor - s->str_start - s->str_esc - 1 + s->utf8_invalid_count);
 		if (len == 0) {
@@ -277,7 +324,22 @@ std:
 			return PHP_JSON_T_STRING;
 		}
 	}
-	<STR_P1>UTF8             { PHP_JSON_CONDITION_GOTO(STR_P1); }
+	<STR_P1>UTF8_1           {
+		PHP_JSON_TOKEN_LOCATION(last_column)++;
+		PHP_JSON_CONDITION_GOTO(STR_P1);
+	}
+	<STR_P1>UTF8_2           {
+		PHP_JSON_TOKEN_LOCATION(last_column) += 1;
+		PHP_JSON_CONDITION_GOTO(STR_P1);
+	}
+	<STR_P1>UTF8_3           {
+		PHP_JSON_TOKEN_LOCATION(last_column) += 1;
+		PHP_JSON_CONDITION_GOTO(STR_P1);
+	}
+	<STR_P1>UTF8_4           {
+		PHP_JSON_TOKEN_LOCATION(last_column) += 1;
+		PHP_JSON_CONDITION_GOTO(STR_P1);
+	}
 	<STR_P1>ANY              {
 		if (s->options & (PHP_JSON_INVALID_UTF8_IGNORE | PHP_JSON_INVALID_UTF8_SUBSTITUTE)) {
 			if (s->options & PHP_JSON_INVALID_UTF8_SUBSTITUTE) {
@@ -295,7 +357,6 @@ std:
 		s->errcode = PHP_JSON_ERROR_UTF8;
 		return PHP_JSON_T_ERROR;
 	}
-
 	<STR_P2_UTF,STR_P2_BIN>UTF16_1             {
 		int utf16 = php_json_ucs2_to_int(s, 2);
 		PHP_JSON_SCANNER_COPY_UTF();
diff --git a/ext/json/php_json.h b/ext/json/php_json.h
index b79c7c836f7..bbe8be9d60a 100644
--- a/ext/json/php_json.h
+++ b/ext/json/php_json.h
@@ -86,6 +86,8 @@ ZEND_BEGIN_MODULE_GLOBALS(json)
 	int encoder_depth;
 	int encode_max_depth;
 	php_json_error_code error_code;
+	size_t error_line;
+	size_t error_column;
 ZEND_END_MODULE_GLOBALS(json)

 PHP_JSON_API ZEND_EXTERN_MODULE_GLOBALS(json)
diff --git a/ext/json/php_json_parser.h b/ext/json/php_json_parser.h
index 8aedce9ac55..8fee3d11c6b 100644
--- a/ext/json/php_json_parser.h
+++ b/ext/json/php_json_parser.h
@@ -50,12 +50,20 @@ typedef struct _php_json_parser_methods {
 	php_json_parser_func_object_end_t object_end;
 } php_json_parser_methods;

+ typedef struct _php_json_parser_location {
+	size_t first_line;
+	size_t first_column;
+	size_t last_line;
+	size_t last_column;
+} php_json_parser_location;
+
 struct _php_json_parser {
 	php_json_scanner scanner;
 	zval *return_value;
 	int depth;
 	int max_depth;
 	php_json_parser_methods methods;
+	php_json_parser_location *location;
 };

 PHP_JSON_API void php_json_parser_init_ex(
@@ -77,6 +85,10 @@ PHP_JSON_API void php_json_parser_init(

 PHP_JSON_API php_json_error_code php_json_parser_error_code(const php_json_parser *parser);

+PHP_JSON_API size_t php_json_parser_error_line(const php_json_parser *parser);
+
+PHP_JSON_API size_t php_json_parser_error_column(const php_json_parser *parser);
+
 PHP_JSON_API int php_json_parse(php_json_parser *parser);

 int php_json_yyparse(php_json_parser *parser);
diff --git a/ext/json/php_json_scanner.h b/ext/json/php_json_scanner.h
index a49be68cd63..a6de149391d 100644
--- a/ext/json/php_json_scanner.h
+++ b/ext/json/php_json_scanner.h
@@ -22,6 +22,17 @@

 typedef unsigned char php_json_ctype;

+typedef struct _php_json_error_location {
+	/** first column of the error */
+	size_t first_column;
+	/** first line of the error */
+	size_t first_line;
+	/** last column of the error */
+	size_t last_column;
+	/** last line of the error */
+	size_t last_line;
+} php_json_error_location;
+
 typedef struct _php_json_scanner {
 	php_json_ctype *cursor;         /* cursor position */
 	php_json_ctype *token;          /* token position */
@@ -35,10 +46,12 @@ typedef struct _php_json_scanner {
 	int state;                      /* condition state */
 	int options;                    /* options */
 	php_json_error_code errcode;    /* error type if there is an error */
+	php_json_error_location errloc; /* error location */
 	int utf8_invalid;               /* whether utf8 is invalid */
 	int utf8_invalid_count;         /* number of extra character for invalid utf8 */
 } php_json_scanner;

+#define PHP_JSON_SCANNER_LOCATION(scanner, slocation) (scanner).errloc.slocation

 void php_json_scanner_init(php_json_scanner *scanner, const char *str, size_t str_len, int options);
 int php_json_scan(php_json_scanner *s);
diff --git a/ext/json/tests/007.phpt b/ext/json/tests/007.phpt
index dea641317e9..9a5ae654d63 100644
--- a/ext/json/tests/007.phpt
+++ b/ext/json/tests/007.phpt
@@ -24,14 +24,14 @@
 string(8) "No error"
 NULL
 int(1)
-string(28) "Maximum stack depth exceeded"
+string(46) "Maximum stack depth exceeded near location 1:2"
 NULL
 int(2)
-string(42) "State mismatch (invalid or malformed JSON)"
+string(60) "State mismatch (invalid or malformed JSON) near location 1:3"
 NULL
 int(3)
-string(53) "Control character error, possibly incorrectly encoded"
+string(71) "Control character error, possibly incorrectly encoded near location 1:2"
 NULL
 int(4)
-string(12) "Syntax error"
+string(30) "Syntax error near location 1:3"
 Done
diff --git a/ext/json/tests/bug62010.phpt b/ext/json/tests/bug62010.phpt
index 2591231dcdd..862d7dc7e2c 100644
--- a/ext/json/tests/bug62010.phpt
+++ b/ext/json/tests/bug62010.phpt
@@ -10,4 +10,4 @@
 --EXPECT--
 NULL
 bool(true)
-string(50) "Single unpaired UTF-16 surrogate in unicode escape"
+string(68) "Single unpaired UTF-16 surrogate in unicode escape near location 1:1"
diff --git a/ext/json/tests/bug68546.phpt b/ext/json/tests/bug68546.phpt
index 8835a72c5ea..1847eabf3a8 100644
--- a/ext/json/tests/bug68546.phpt
+++ b/ext/json/tests/bug68546.phpt
@@ -5,7 +5,7 @@

 var_dump(json_decode('{"key": {"\u0000": "aa"}}'));
 var_dump(json_last_error() === JSON_ERROR_INVALID_PROPERTY_NAME);
-var_dump(json_decode('[{"key1": 0, "\u0000": 1}]'));
+var_dump(json_decode('[{"key1": 0, "\u1234": 1, "\u0000": 1}]'));
 var_dump(json_last_error() === JSON_ERROR_INVALID_PROPERTY_NAME);
 var_dump(json_last_error_msg());

@@ -16,5 +16,5 @@
 bool(true)
 NULL
 bool(true)
-string(36) "The decoded property name is invalid"
+string(55) "The decoded property name is invalid near location 1:27"
 Done
diff --git a/ext/json/tests/json_decode_exceptions.phpt b/ext/json/tests/json_decode_exceptions.phpt
index 7dc2e7408a0..d53941682e4 100644
--- a/ext/json/tests/json_decode_exceptions.phpt
+++ b/ext/json/tests/json_decode_exceptions.phpt
@@ -13,7 +13,7 @@
 --EXPECTF--
 object(JsonException)#1 (7) {
   ["message":protected]=>
-  string(12) "Syntax error"
+  string(30) "Syntax error near location 1:2"
   ["string":"Exception":private]=>
   string(0) ""
   ["code":protected]=>
diff --git a/ext/json/tests/json_last_error_msg_error_location_001.phpt b/ext/json/tests/json_last_error_msg_error_location_001.phpt
new file mode 100644
index 00000000000..e0553f9f7d6
--- /dev/null
+++ b/ext/json/tests/json_last_error_msg_error_location_001.phpt
@@ -0,0 +1,121 @@
+--TEST--
+json_last_error_msg() - Error location reporting with ASCII characters
+--FILE--
+<?php
+
+require_once("json_validate_requires.inc");
+
+echo "Testing errors at various locations with ASCII characters\n\n";
+
+// Error at start
+echo "Error at position 1:1:\n";
+json_validate_trycatchdump("-");
+
+// Error in middle of simple object
+echo "\nError at position 1:10:\n";
+json_validate_trycatchdump('{"name": "value}');
+
+// Missing colon
+echo "\nError at position 1:9:\n";
+json_validate_trycatchdump('{"name" "value"}');
+
+// Extra comma
+echo "\nError at position 1:16:\n";
+json_validate_trycatchdump('{"name": "val",}');
+
+// Missing closing bracket in array
+echo "\nError at position 1:15:\n";
+json_validate_trycatchdump('[1, 2, 3, 4, 5');
+
+// Invalid value
+echo "\nError at position 1:10:\n";
+json_validate_trycatchdump('{"test": undefined}');
+
+// Trailing comma in array
+echo "\nError at position 1:7:\n";
+json_validate_trycatchdump('[1, 2,]');
+
+// Single quote instead of double quote
+echo "\nError at position 1:2:\n";
+json_validate_trycatchdump("{'key': 'value'}");
+
+// Missing closing brace
+echo "\nError at position 1:16:\n";
+json_validate_trycatchdump('{"key": "value"');
+
+// Double comma
+echo "\nError at position 1:4:\n";
+json_validate_trycatchdump('[1,, 2]');
+
+// Invalid escape sequence
+echo "\nError at position 1:10:\n";
+json_validate_trycatchdump('{"test": "\x"}');
+
+// Unescaped control character
+echo "\nError at position 1:10:\n";
+json_validate_trycatchdump('{"test": "' . "\n" . '"}');
+
+?>
+--EXPECT--
+Testing errors at various locations with ASCII characters
+
+Error at position 1:1:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:1"
+
+Error at position 1:10:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
+Error at position 1:9:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:9"
+
+Error at position 1:16:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:16"
+
+Error at position 1:15:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:15"
+
+Error at position 1:10:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:10"
+
+Error at position 1:7:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:7"
+
+Error at position 1:2:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:2"
+
+Error at position 1:16:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:16"
+
+Error at position 1:4:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:4"
+
+Error at position 1:10:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:10"
+
+Error at position 1:10:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
diff --git a/ext/json/tests/json_last_error_msg_error_location_002.phpt b/ext/json/tests/json_last_error_msg_error_location_002.phpt
new file mode 100644
index 00000000000..df7fc981ccb
--- /dev/null
+++ b/ext/json/tests/json_last_error_msg_error_location_002.phpt
@@ -0,0 +1,103 @@
+--TEST--
+json_last_error_msg() - Error location reporting with Unicode UTF-8 characters
+--FILE--
+<?php
+
+require_once("json_validate_requires.inc");
+
+echo "Testing error locations with Unicode UTF-8 characters\n\n";
+
+// Error after UTF-8 characters (Japanese)
+echo "Error after Japanese characters:\n";
+json_validate_trycatchdump('{"プレスキット": "value}');
+
+// Error after UTF-8 characters (Russian)
+echo "\nError after Russian characters:\n";
+json_validate_trycatchdump('{"имя": "значение}');
+
+// Error after UTF-8 characters (Chinese)
+echo "\nError after Chinese characters:\n";
+json_validate_trycatchdump('{"名字": "值}');
+
+// Error after UTF-8 characters (Arabic)
+echo "\nError after Arabic characters:\n";
+json_validate_trycatchdump('{"اسم": "قيمة}');
+
+// Error after UTF-8 characters (Emoji)
+echo "\nError after Emoji:\n";
+json_validate_trycatchdump('{"emoji": "😀🎉}');
+
+// Mixed ASCII and UTF-8
+echo "\nError in mixed ASCII and UTF-8:\n";
+json_validate_trycatchdump('{"name": "John", "город": "Москва}');
+
+// UTF-8 with escaped sequences
+echo "\nError with UTF-8 escaped sequences:\n";
+json_validate_trycatchdump('{"test": "\u30d7\u30ec\u30b9}');
+
+// Multiple UTF-8 keys with error
+echo "\nError in object with multiple UTF-8 keys:\n";
+json_validate_trycatchdump('{"キー1": "値1", "キー2": "値2}');
+
+// Array with UTF-8 and error
+echo "\nError in array with UTF-8 strings:\n";
+json_validate_trycatchdump('["文字列1", "文字列2", "文字列3}');
+
+// Nested object with UTF-8 and error
+echo "\nError in nested object with UTF-8:\n";
+json_validate_trycatchdump('{"外部": {"内部": "値}');
+
+?>
+--EXPECT--
+Testing error locations with Unicode UTF-8 characters
+
+Error after Japanese characters:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:12"
+
+Error after Russian characters:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:9"
+
+Error after Chinese characters:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:8"
+
+Error after Arabic characters:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:9"
+
+Error after Emoji:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:11"
+
+Error in mixed ASCII and UTF-8:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:27"
+
+Error with UTF-8 escaped sequences:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
+Error in object with multiple UTF-8 keys:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:22"
+
+Error in array with UTF-8 strings:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:18"
+
+Error in nested object with UTF-8:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:15"
+
diff --git a/ext/json/tests/json_last_error_msg_error_location_003.phpt b/ext/json/tests/json_last_error_msg_error_location_003.phpt
new file mode 100644
index 00000000000..ec5e6b9b4d6
--- /dev/null
+++ b/ext/json/tests/json_last_error_msg_error_location_003.phpt
@@ -0,0 +1,72 @@
+--TEST--
+json_last_error_msg() - Error location reporting with multi-line JSON
+--FILE--
+<?php
+
+require_once("json_validate_requires.inc");
+
+echo "Testing error locations in multi-line JSON\n\n";
+
+// Error on line 2
+echo "Error on line 2, column 13:\n";
+$json = '{
+    "name": "value
+}';
+json_validate_trycatchdump($json);
+
+// Error on line 3
+echo "\nError on line 3, column 12:\n";
+$json = '{
+    "key1": "value1",
+    "key2" "value2"
+}';
+json_validate_trycatchdump($json);
+
+// Error on line 5 in nested structure
+echo "\nError on line 5, column 26:\n";
+$json = '{
+    "outer": {
+        "inner": {
+            "deep": {
+                "value": "unclosed
+            }
+        }
+    }
+}';
+json_validate_trycatchdump($json);
+
+// Error on last line
+echo "\nError on line 7, column 1:\n";
+$json = '{
+    "key1": "value1",
+    "key2": "value2",
+    "key3": "value3",
+    "key4": "value4",
+    "key5": "value5"
+';
+json_validate_trycatchdump($json);
+
+?>
+--EXPECT--
+Testing error locations in multi-line JSON
+
+Error on line 2, column 13:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 2:13"
+
+Error on line 3, column 12:
+bool(false)
+int(4)
+string(31) "Syntax error near location 3:12"
+
+Error on line 5, column 26:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 5:26"
+
+Error on line 7, column 1:
+bool(false)
+int(4)
+string(30) "Syntax error near location 7:1"
+
diff --git a/ext/json/tests/json_last_error_msg_error_location_004.phpt b/ext/json/tests/json_last_error_msg_error_location_004.phpt
new file mode 100644
index 00000000000..165449600fb
--- /dev/null
+++ b/ext/json/tests/json_last_error_msg_error_location_004.phpt
@@ -0,0 +1,93 @@
+--TEST--
+json_last_error_msg() - Error location reporting with deeply nested structures
+--FILE--
+<?php
+
+require_once("json_validate_requires.inc");
+
+echo "Testing error locations in deeply nested structures\n\n";
+
+// Error in deeply nested object
+echo "Error in deeply nested object:\n";
+$json = '{"a": {"b": {"c": {"d": {"e": "value}}}}}';
+json_validate_trycatchdump($json);
+
+// Error in deeply nested array
+echo "\nError in deeply nested array:\n";
+$json = '[[[[[1, 2, 3, 4, 5]]]]]';
+json_validate_trycatchdump($json);
+
+// Error in mixed nested structures
+echo "\nError in mixed nested structures:\n";
+$json = '{"arr": [{"obj": [{"key": "val"}]}]}';
+json_validate_trycatchdump($json);
+
+// Error at the end of deep nesting
+echo "\nError at end of deep nesting:\n";
+$json = '{"level1": {"level2": {"level3": {"level4": "value"}}}}';
+json_validate_trycatchdump($json);
+
+// Error in middle of deep nesting
+echo "\nError in middle of deep nesting:\n";
+$json = '{"l1": {"l2": {"l3" {"l4": "val"}}}}';
+json_validate_trycatchdump($json);
+
+// Complex structure with error
+echo "\nError in complex structure:\n";
+$json = '{"users": [{"name": "John", "age": 30}, {"name": "Jane", "age": 25}], "count": 2, "status": "active}';
+json_validate_trycatchdump($json);
+
+// Array of objects with error
+echo "\nError in array of objects:\n";
+$json = '[{"id": 1, "name": "A"}, {"id": 2, "name": "B"}, {"id": 3, "name": "C}]';
+json_validate_trycatchdump($json);
+
+// Object with array values error
+echo "\nError in object with array values:\n";
+$json = '{"numbers": [1, 2, 3], "strings": ["a", "b", "c"], "booleans": [true, false, true}';
+json_validate_trycatchdump($json);
+
+?>
+--EXPECT--
+Testing error locations in deeply nested structures
+
+Error in deeply nested object:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:31"
+
+Error in deeply nested array:
+bool(true)
+int(0)
+string(8) "No error"
+
+Error in mixed nested structures:
+bool(true)
+int(0)
+string(8) "No error"
+
+Error at end of deep nesting:
+bool(true)
+int(0)
+string(8) "No error"
+
+Error in middle of deep nesting:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:21"
+
+Error in complex structure:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:93"
+
+Error in array of objects:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:68"
+
+Error in object with array values:
+bool(false)
+int(2)
+string(61) "State mismatch (invalid or malformed JSON) near location 1:82"
+
diff --git a/ext/json/tests/json_last_error_msg_error_location_005.phpt b/ext/json/tests/json_last_error_msg_error_location_005.phpt
new file mode 100644
index 00000000000..d12ce387e73
--- /dev/null
+++ b/ext/json/tests/json_last_error_msg_error_location_005.phpt
@@ -0,0 +1,103 @@
+--TEST--
+json_last_error_msg() - Error location reporting with UTF-16 surrogate pairs
+--FILE--
+<?php
+
+require_once("json_validate_requires.inc");
+
+echo "Testing error locations with UTF-16 surrogate pairs and escape sequences\n\n";
+
+// Error after UTF-16 escaped characters
+echo "Error after UTF-16 escaped emoji:\n";
+json_validate_trycatchdump('{"emoji": "\uD83D\uDE00}');
+
+// Error after multiple UTF-16 pairs
+echo "\nError after multiple UTF-16 pairs:\n";
+json_validate_trycatchdump('{"test": "\uD83D\uDE00\uD83C\uDF89}');
+
+// Mixed UTF-8 and UTF-16 escapes
+echo "\nError with mixed UTF-8 and UTF-16:\n";
+json_validate_trycatchdump('{"mixed": "Hello \u4E16\u754C world}');
+
+// UTF-16 in key and value
+echo "\nError with UTF-16 in key:\n";
+json_validate_trycatchdump('{"\u30D7\u30EC\u30B9": "value}');
+
+// Multiple keys with UTF-16
+echo "\nError with multiple UTF-16 keys:\n";
+json_validate_trycatchdump('{"\u0041\u0042": "val1", "\u0043\u0044": "val2}');
+
+// BMP characters (Basic Multilingual Plane)
+echo "\nError with BMP characters:\n";
+json_validate_trycatchdump('{"test": "\u0048\u0065\u006C\u006C\u006F}');
+
+// Supplementary plane characters (beyond BMP)
+echo "\nError with supplementary plane:\n";
+json_validate_trycatchdump('{"music": "\uD834\uDD1E}');
+
+// Array with UTF-16 escaped strings
+echo "\nError in array with UTF-16:\n";
+json_validate_trycatchdump('["\u0031", "\u0032", "\u0033}');
+
+// Nested with UTF-16
+echo "\nError in nested structure with UTF-16:\n";
+json_validate_trycatchdump('{"outer": {"\u6D4B\u8BD5": "value}');
+
+// UTF-16 with control characters
+echo "\nError with UTF-16 and control chars:\n";
+json_validate_trycatchdump('{"data": "\u0009\u000A\u000D}');
+
+?>
+--EXPECT--
+Testing error locations with UTF-16 surrogate pairs and escape sequences
+
+Error after UTF-16 escaped emoji:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:11"
+
+Error after multiple UTF-16 pairs:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
+Error with mixed UTF-8 and UTF-16:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:11"
+
+Error with UTF-16 in key:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:9"
+
+Error with multiple UTF-16 keys:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:22"
+
+Error with BMP characters:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
+Error with supplementary plane:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:11"
+
+Error in array with UTF-16:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:12"
+
+Error in nested structure with UTF-16:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:18"
+
+Error with UTF-16 and control chars:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
diff --git a/ext/json/tests/json_last_error_msg_error_location_006.phpt b/ext/json/tests/json_last_error_msg_error_location_006.phpt
new file mode 100644
index 00000000000..e6aab1af8f2
--- /dev/null
+++ b/ext/json/tests/json_last_error_msg_error_location_006.phpt
@@ -0,0 +1,152 @@
+--TEST--
+json_last_error_msg() - Error location reporting edge cases
+--FILE--
+<?php
+
+require_once("json_validate_requires.inc");
+
+echo "Testing error location edge cases\n\n";
+
+// Error at very start
+echo "Error at position 1:1:\n";
+json_validate_trycatchdump('');
+
+// Error at position 1:1 with single character
+echo "\nError at position 1:1 with invalid char:\n";
+json_validate_trycatchdump('x');
+
+// Error after whitespace
+echo "\nError after leading whitespace:\n";
+json_validate_trycatchdump('   {');
+
+// Error with only whitespace
+echo "\nError with tabs and spaces:\n";
+json_validate_trycatchdump("  \t  \n  ");
+
+// Error after newlines
+echo "\nError after multiple newlines:\n";
+json_validate_trycatchdump("\n\n\n{");
+
+// Long string with error at end
+echo "\nError at end of long string:\n";
+$longString = '{"key": "' . str_repeat('a', 1000) . '"}';
+// Remove closing brace to create error
+$longString = substr($longString, 0, -1);
+json_validate_trycatchdump($longString);
+
+// Error with very long key name
+echo "\nError with very long key:\n";
+$longKey = '{"' . str_repeat('x', 500) . '": "value}';
+json_validate_trycatchdump($longKey);
+
+// Empty object followed by error
+echo "\nError after empty object:\n";
+json_validate_trycatchdump('{}x');
+
+// Empty array followed by error
+echo "\nError after empty array:\n";
+json_validate_trycatchdump('[]x');
+
+// Multiple values (invalid JSON)
+echo "\nError with multiple root values:\n";
+json_validate_trycatchdump('{}{}');
+
+// Number followed by invalid content
+echo "\nError after valid number:\n";
+json_validate_trycatchdump('123x');
+
+// Boolean followed by invalid content
+echo "\nError after valid boolean:\n";
+json_validate_trycatchdump('truex');
+
+// Null followed by invalid content
+echo "\nError after valid null:\n";
+json_validate_trycatchdump('nullx');
+
+// String followed by invalid content
+echo "\nError after valid string:\n";
+json_validate_trycatchdump('"test"x');
+
+// Error with mixed whitespace
+echo "\nError with mixed whitespace:\n";
+json_validate_trycatchdump("{\n\t\"key\": \n\t\"value\n}");
+
+?>
+--EXPECT--
+Testing error location edge cases
+
+Error at position 1:1:
+bool(false)
+int(4)
+string(12) "Syntax error"
+
+Error at position 1:1 with invalid char:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:1"
+
+Error after leading whitespace:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:5"
+
+Error with tabs and spaces:
+bool(false)
+int(4)
+string(30) "Syntax error near location 2:3"
+
+Error after multiple newlines:
+bool(false)
+int(4)
+string(30) "Syntax error near location 4:2"
+
+Error at end of long string:
+bool(false)
+int(4)
+string(33) "Syntax error near location 1:1011"
+
+Error with very long key:
+bool(false)
+int(3)
+string(73) "Control character error, possibly incorrectly encoded near location 1:506"
+
+Error after empty object:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:3"
+
+Error after empty array:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:3"
+
+Error with multiple root values:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:3"
+
+Error after valid number:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:4"
+
+Error after valid boolean:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:5"
+
+Error after valid null:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:5"
+
+Error after valid string:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:7"
+
+Error with mixed whitespace:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 3:2"
+
diff --git a/ext/json/tests/json_last_error_msg_error_location_007.phpt b/ext/json/tests/json_last_error_msg_error_location_007.phpt
new file mode 100644
index 00000000000..0e24889bbbb
--- /dev/null
+++ b/ext/json/tests/json_last_error_msg_error_location_007.phpt
@@ -0,0 +1,178 @@
+--TEST--
+json_last_error_msg() - Error location with various error types
+--FILE--
+<?php
+
+require_once("json_validate_requires.inc");
+
+echo "Testing error locations with different error types\n\n";
+
+// State mismatch errors
+echo "State mismatch - expected value:\n";
+json_validate_trycatchdump('{"key": }');
+
+echo "\nState mismatch - expected key:\n";
+json_validate_trycatchdump('{: "value"}');
+
+echo "\nState mismatch - trailing comma in object:\n";
+json_validate_trycatchdump('{"a": 1, "b": 2,}');
+
+echo "\nState mismatch - trailing comma in array:\n";
+json_validate_trycatchdump('[1, 2, 3,]');
+
+// Number format errors
+echo "\nInvalid number format - leading zero:\n";
+json_validate_trycatchdump('{"num": 01}');
+
+echo "\nInvalid number format - multiple decimals:\n";
+json_validate_trycatchdump('{"num": 1.2.3}');
+
+echo "\nInvalid number format - incomplete exponent:\n";
+json_validate_trycatchdump('{"num": 1e}');
+
+echo "\nInvalid number format - double sign:\n";
+json_validate_trycatchdump('{"num": --1}');
+
+// String errors
+echo "\nUnclosed string:\n";
+json_validate_trycatchdump('{"key": "value');
+
+echo "\nInvalid escape sequence:\n";
+json_validate_trycatchdump('{"key": "\\q"}');
+
+echo "\nIncomplete unicode escape:\n";
+json_validate_trycatchdump('{"key": "\\u123"}');
+
+echo "\nInvalid unicode escape:\n";
+json_validate_trycatchdump('{"key": "\\uGGGG"}');
+
+// Keyword errors
+echo "\nInvalid true keyword:\n";
+json_validate_trycatchdump('{"val": tru}');
+
+echo "\nInvalid false keyword:\n";
+json_validate_trycatchdump('{"val": fals}');
+
+echo "\nInvalid null keyword:\n";
+json_validate_trycatchdump('{"val": nul}');
+
+// Structure errors
+echo "\nMismatched brackets - ] instead of }:\n";
+json_validate_trycatchdump('{"key": "val"]');
+
+echo "\nMismatched brackets - } instead of ]:\n";
+json_validate_trycatchdump('["val"}');
+
+echo "\nExtra closing bracket:\n";
+json_validate_trycatchdump('{"key": "val"}}');
+
+echo "\nMissing comma between elements:\n";
+json_validate_trycatchdump('[1 2 3]');
+
+echo "\nMissing comma between object properties:\n";
+json_validate_trycatchdump('{"a": 1 "b": 2}');
+
+?>
+--EXPECT--
+Testing error locations with different error types
+
+State mismatch - expected value:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:9"
+
+State mismatch - expected key:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:2"
+
+State mismatch - trailing comma in object:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:17"
+
+State mismatch - trailing comma in array:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:10"
+
+Invalid number format - leading zero:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:10"
+
+Invalid number format - multiple decimals:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:12"
+
+Invalid number format - incomplete exponent:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:10"
+
+Invalid number format - double sign:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:9"
+
+Unclosed string:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:9"
+
+Invalid escape sequence:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:9"
+
+Incomplete unicode escape:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:9"
+
+Invalid unicode escape:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:9"
+
+Invalid true keyword:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:9"
+
+Invalid false keyword:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:9"
+
+Invalid null keyword:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:9"
+
+Mismatched brackets - ] instead of }:
+bool(false)
+int(2)
+string(61) "State mismatch (invalid or malformed JSON) near location 1:14"
+
+Mismatched brackets - } instead of ]:
+bool(false)
+int(2)
+string(60) "State mismatch (invalid or malformed JSON) near location 1:7"
+
+Extra closing bracket:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:15"
+
+Missing comma between elements:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:4"
+
+Missing comma between object properties:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:9"
+
diff --git a/ext/json/tests/json_last_error_msg_error_location_008.phpt b/ext/json/tests/json_last_error_msg_error_location_008.phpt
new file mode 100644
index 00000000000..4d8a1012316
--- /dev/null
+++ b/ext/json/tests/json_last_error_msg_error_location_008.phpt
@@ -0,0 +1,182 @@
+--TEST--
+json_last_error_msg() - Error location with mixed UTF-8 multi-byte characters
+--FILE--
+<?php
+
+require_once("json_validate_requires.inc");
+
+echo "Testing error locations with various UTF-8 multi-byte character widths\n\n";
+
+// 2-byte UTF-8 characters (Latin Extended)
+echo "Error with 2-byte UTF-8 (Latin Extended):\n";
+json_validate_trycatchdump('{"café": "value}');
+
+echo "\nError with 2-byte UTF-8 (Greek):\n";
+json_validate_trycatchdump('{"Ελληνικά": "value}');
+
+echo "\nError with 2-byte UTF-8 (Cyrillic):\n";
+json_validate_trycatchdump('{"Привет": "мир}');
+
+// 3-byte UTF-8 characters (CJK)
+echo "\nError with 3-byte UTF-8 (Chinese):\n";
+json_validate_trycatchdump('{"中文": "测试}');
+
+echo "\nError with 3-byte UTF-8 (Japanese Hiragana):\n";
+json_validate_trycatchdump('{"ひらがな": "テスト}');
+
+echo "\nError with 3-byte UTF-8 (Japanese Katakana):\n";
+json_validate_trycatchdump('{"カタカナ": "テスト}');
+
+echo "\nError with 3-byte UTF-8 (Korean):\n";
+json_validate_trycatchdump('{"한글": "테스트}');
+
+// 4-byte UTF-8 characters (Emoji and special symbols)
+echo "\nError with 4-byte UTF-8 (Emoji faces):\n";
+json_validate_trycatchdump('{"emoji": "😀😃😄😁}');
+
+echo "\nError with 4-byte UTF-8 (Emoji objects):\n";
+json_validate_trycatchdump('{"objects": "🎉🎊🎁🎈}');
+
+echo "\nError with 4-byte UTF-8 (Mathematical symbols):\n";
+json_validate_trycatchdump('{"math": "𝕒𝕓𝕔𝕕}');
+
+// Mixed byte-width characters
+echo "\nError with mixed 1-2-3 byte UTF-8:\n";
+json_validate_trycatchdump('{"mix": "Aéø中文}');
+
+echo "\nError with mixed 2-3-4 byte UTF-8:\n";
+json_validate_trycatchdump('{"mix": "Привет中文😀}');
+
+echo "\nError with all byte widths:\n";
+json_validate_trycatchdump('{"all": "Aéø中文😀}');
+
+// UTF-8 in keys at different positions
+echo "\nError with UTF-8 key at start:\n";
+json_validate_trycatchdump('{"🔑": "value}');
+
+echo "\nError with multiple UTF-8 keys:\n";
+json_validate_trycatchdump('{"キー1": "値1", "キー2": "値2", "キー3": "値3}');
+
+// Array with mixed UTF-8
+echo "\nError in array with mixed UTF-8:\n";
+json_validate_trycatchdump('["ASCII", "café", "中文", "😀}');
+
+// Complex nested structure with UTF-8
+echo "\nError in nested structure with various UTF-8:\n";
+json_validate_trycatchdump('{"外层": {"中层": {"内层": "值}}}');
+
+// UTF-8 with special combining characters
+echo "\nError with combining diacritical marks:\n";
+json_validate_trycatchdump('{"tëst": "vâlue}');
+
+// Right-to-left languages
+echo "\nError with Hebrew:\n";
+json_validate_trycatchdump('{"שלום": "עולם}');
+
+echo "\nError with Arabic with diacritics:\n";
+json_validate_trycatchdump('{"مرحبا": "عالم}');
+
+?>
+--EXPECT--
+Testing error locations with various UTF-8 multi-byte character widths
+
+Error with 2-byte UTF-8 (Latin Extended):
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
+Error with 2-byte UTF-8 (Greek):
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:14"
+
+Error with 2-byte UTF-8 (Cyrillic):
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:12"
+
+Error with 3-byte UTF-8 (Chinese):
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:8"
+
+Error with 3-byte UTF-8 (Japanese Hiragana):
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
+Error with 3-byte UTF-8 (Japanese Katakana):
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
+Error with 3-byte UTF-8 (Korean):
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:8"
+
+Error with 4-byte UTF-8 (Emoji faces):
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:11"
+
+Error with 4-byte UTF-8 (Emoji objects):
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:13"
+
+Error with 4-byte UTF-8 (Mathematical symbols):
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
+Error with mixed 1-2-3 byte UTF-8:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:9"
+
+Error with mixed 2-3-4 byte UTF-8:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:9"
+
+Error with all byte widths:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:9"
+
+Error with UTF-8 key at start:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:7"
+
+Error with multiple UTF-8 keys:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:35"
+
+Error in array with mixed UTF-8:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:25"
+
+Error in nested structure with various UTF-8:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:22"
+
+Error with combining diacritical marks:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
+Error with Hebrew:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
+Error with Arabic with diacritics:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:11"
+
diff --git a/ext/json/tests/json_last_error_msg_error_location_009.phpt b/ext/json/tests/json_last_error_msg_error_location_009.phpt
new file mode 100644
index 00000000000..406179693ef
--- /dev/null
+++ b/ext/json/tests/json_last_error_msg_error_location_009.phpt
@@ -0,0 +1,110 @@
+--TEST--
+json_last_error_msg() - Error location with depth errors and complex nesting
+--FILE--
+<?php
+
+require_once("json_validate_requires.inc");
+
+echo "Testing error locations with depth-related issues\n\n";
+
+// Test max depth error with location
+echo "Max depth error at specific location:\n";
+$json = '{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":"val"}}}}}}}}}}';
+json_validate_trycatchdump($json, 5);
+
+echo "\nMax depth error in array:\n";
+$json = '[[[[[[[[[[10]]]]]]]]]]';
+json_validate_trycatchdump($json, 5);
+
+echo "\nMax depth error with mixed structures:\n";
+$json = '[{"a":[{"b":[{"c":"val"}]}]}]';
+json_validate_trycatchdump($json, 3);
+
+// Syntax error at deep level
+echo "\nSyntax error at deep nesting level:\n";
+$json = '{"l1":{"l2":{"l3":{"l4":{"l5":"unclosed}}}}}';
+json_validate_trycatchdump($json);
+
+echo "\nSyntax error in deep array:\n";
+$json = '[[[[["value]]]]]';
+json_validate_trycatchdump($json);
+
+// Valid deep structure followed by error
+echo "\nError after valid deep structure:\n";
+$json = '{"valid":{"nested":{"structure":"ok"}},"error":"unclosed}';
+json_validate_trycatchdump($json);
+
+// Multiple nested objects with error in middle
+echo "\nError in middle of nested structure:\n";
+$json = '{"outer":{"middle":{"inner" "value"}}}';
+json_validate_trycatchdump($json);
+
+// Array with nested objects and error
+echo "\nError in array with nested objects:\n";
+$json = '[{"a":{"b":"val"}},{"c":{"d":"val}}}]';
+json_validate_trycatchdump($json);
+
+// Deep structure with UTF-8 and error
+echo "\nError in deep UTF-8 structure:\n";
+$json = '{"外":{"中":{"内":"値}}}';
+json_validate_trycatchdump($json);
+
+// Very deep valid structure (should pass)
+echo "\nValid deep structure within limit:\n";
+$json = '{"a":{"b":{"c":{"d":"val"}}}}';
+json_validate_trycatchdump($json, 10);
+
+?>
+--EXPECT--
+Testing error locations with depth-related issues
+
+Max depth error at specific location:
+bool(false)
+int(1)
+string(47) "Maximum stack depth exceeded near location 1:21"
+
+Max depth error in array:
+bool(false)
+int(1)
+string(46) "Maximum stack depth exceeded near location 1:5"
+
+Max depth error with mixed structures:
+bool(false)
+int(1)
+string(46) "Maximum stack depth exceeded near location 1:7"
+
+Syntax error at deep nesting level:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:31"
+
+Syntax error in deep array:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:6"
+
+Error after valid deep structure:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:48"
+
+Error in middle of nested structure:
+bool(false)
+int(4)
+string(31) "Syntax error near location 1:29"
+
+Error in array with nested objects:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:30"
+
+Error in deep UTF-8 structure:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:16"
+
+Valid deep structure within limit:
+bool(true)
+int(0)
+string(8) "No error"
+
diff --git a/ext/json/tests/json_last_error_msg_error_location_010.phpt b/ext/json/tests/json_last_error_msg_error_location_010.phpt
new file mode 100644
index 00000000000..10857020583
--- /dev/null
+++ b/ext/json/tests/json_last_error_msg_error_location_010.phpt
@@ -0,0 +1,164 @@
+--TEST--
+json_last_error_msg() - Error location with whitespace variations
+--FILE--
+<?php
+
+require_once("json_validate_requires.inc");
+
+echo "Testing error locations with various whitespace patterns\n\n";
+
+// Error after spaces
+echo "Error after multiple spaces:\n";
+json_validate_trycatchdump('     {');
+
+// Error after tabs
+echo "\nError after tabs:\n";
+json_validate_trycatchdump("\t\t\t{");
+
+// Error after mixed whitespace
+echo "\nError after mixed whitespace:\n";
+json_validate_trycatchdump("  \t  {");
+
+// Error on new line
+echo "\nError on second line:\n";
+json_validate_trycatchdump("{\n}x");
+
+// Error with CRLF line endings
+echo "\nError with CRLF line endings:\n";
+json_validate_trycatchdump("{\r\n\"key\": \"value\r\n}");
+
+// Error with multiple blank lines
+echo "\nError after blank lines:\n";
+json_validate_trycatchdump("\n\n\n{");
+
+// Whitespace inside strings (should not affect location)
+echo "\nError in string with spaces:\n";
+json_validate_trycatchdump('{"key": "value with    spaces}');
+
+// Whitespace before and after colons
+echo "\nError with whitespace around colon:\n";
+json_validate_trycatchdump('{"key"  :  "value}');
+
+// Whitespace before and after commas
+echo "\nError with whitespace around comma:\n";
+json_validate_trycatchdump('{"a": 1  ,  "b": 2  ,  "c": 3}');
+
+// Pretty printed JSON with error
+echo "\nError in pretty printed JSON:\n";
+$json = '{
+  "name": "John",
+  "age": 30,
+  "city": "New York
+}';
+json_validate_trycatchdump($json);
+
+// Heavily indented JSON with error
+echo "\nError in heavily indented JSON:\n";
+$json = '{
+    "level1": {
+        "level2": {
+            "level3": {
+                "value": "test
+            }
+        }
+    }
+}';
+json_validate_trycatchdump($json);
+
+// Mixed tabs and spaces with error
+echo "\nError with mixed tabs and spaces:\n";
+$json = "{\n\t  \"key1\": \"val1\",\n  \t\"key2\": \"val2\n}";
+json_validate_trycatchdump($json);
+
+// Whitespace-only line before error
+echo "\nError after whitespace-only line:\n";
+$json = "{\n    \n    \"key\": \"value\n}";
+json_validate_trycatchdump($json);
+
+// No whitespace (compact JSON) with error
+echo "\nError in compact JSON:\n";
+json_validate_trycatchdump('{"a":"b","c":"d","e":"f}');
+
+// Unicode whitespace characters (if supported)
+echo "\nError with regular spaces:\n";
+json_validate_trycatchdump('{ "key": "value}');
+
+?>
+--EXPECT--
+Testing error locations with various whitespace patterns
+
+Error after multiple spaces:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:7"
+
+Error after tabs:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:5"
+
+Error after mixed whitespace:
+bool(false)
+int(4)
+string(30) "Syntax error near location 1:7"
+
+Error on second line:
+bool(false)
+int(4)
+string(30) "Syntax error near location 2:2"
+
+Error with CRLF line endings:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 2:8"
+
+Error after blank lines:
+bool(false)
+int(4)
+string(30) "Syntax error near location 4:2"
+
+Error in string with spaces:
+bool(false)
+int(3)
+string(71) "Control character error, possibly incorrectly encoded near location 1:9"
+
+Error with whitespace around colon:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:12"
+
+Error with whitespace around comma:
+bool(true)
+int(0)
+string(8) "No error"
+
+Error in pretty printed JSON:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 4:11"
+
+Error in heavily indented JSON:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 5:26"
+
+Error with mixed tabs and spaces:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 3:12"
+
+Error after whitespace-only line:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 3:12"
+
+Error in compact JSON:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:22"
+
+Error with regular spaces:
+bool(false)
+int(3)
+string(72) "Control character error, possibly incorrectly encoded near location 1:10"
+
diff --git a/ext/json/tests/json_validate_002.phpt b/ext/json/tests/json_validate_002.phpt
index 53f4e4f2c2e..423564c4ad7 100644
--- a/ext/json/tests/json_validate_002.phpt
+++ b/ext/json/tests/json_validate_002.phpt
@@ -23,13 +23,13 @@
 string(12) "Syntax error"
 bool(false)
 int(4)
-string(12) "Syntax error"
+string(30) "Syntax error near location 1:1"
 bool(false)
 int(4)
 string(12) "Syntax error"
 bool(false)
 int(1)
-string(28) "Maximum stack depth exceeded"
+string(46) "Maximum stack depth exceeded near location 1:1"
 bool(true)
 int(0)
 string(8) "No error"
@@ -44,7 +44,7 @@
 string(8) "No error"
 bool(false)
 int(4)
-string(12) "Syntax error"
+string(30) "Syntax error near location 1:1"
 bool(true)
 int(0)
 string(8) "No error"
diff --git a/ext/json/tests/json_validate_004.phpt b/ext/json/tests/json_validate_004.phpt
index d8a798d9432..bd807defa14 100644
--- a/ext/json/tests/json_validate_004.phpt
+++ b/ext/json/tests/json_validate_004.phpt
@@ -23,16 +23,16 @@
 Testing Invalid UTF-8
 bool(false)
 int(5)
-string(56) "Malformed UTF-8 characters, possibly incorrectly encoded"
+string(74) "Malformed UTF-8 characters, possibly incorrectly encoded near location 1:1"
 bool(false)
 int(5)
-string(56) "Malformed UTF-8 characters, possibly incorrectly encoded"
+string(74) "Malformed UTF-8 characters, possibly incorrectly encoded near location 1:1"
 bool(false)
 int(5)
-string(56) "Malformed UTF-8 characters, possibly incorrectly encoded"
+string(74) "Malformed UTF-8 characters, possibly incorrectly encoded near location 1:1"
 bool(false)
 int(5)
-string(56) "Malformed UTF-8 characters, possibly incorrectly encoded"
+string(74) "Malformed UTF-8 characters, possibly incorrectly encoded near location 1:2"
 bool(true)
 int(0)
 string(8) "No error"