This is an automated email from the ASF dual-hosted git repository.

afs pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit 12c0dfb57da36a0fef8378d362d3876fcdb36372
Author: Andy Seaborne <[email protected]>
AuthorDate: Fri May 22 08:32:25 2026 +0100

    GH-3931: No surrogates - SPARQL
---
 .../sparql/expr/TestSPARQLKeywordFunctions.java    |  25 ++-
 .../apache/jena/sparql/syntax/TestQueryParser.java |  32 ++--
 .../java/org/apache/jena/atlas/lib/EscapeStr.java  |  61 +++----
 .../org/apache/jena/atlas/lib/TestEscapeStr.java   | 175 ++++++++++++---------
 4 files changed, 160 insertions(+), 133 deletions(-)

diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestSPARQLKeywordFunctions.java
 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestSPARQLKeywordFunctions.java
index 4502fe4639..529f392260 100644
--- 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestSPARQLKeywordFunctions.java
+++ 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestSPARQLKeywordFunctions.java
@@ -24,7 +24,6 @@ package org.apache.jena.sparql.expr;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
 
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
@@ -199,17 +198,15 @@ public class TestSPARQLKeywordFunctions
     // As surrogate pair: 0xD83D 0xDC6A
     // Written here in forms which protect against binary file corruption.
 
-    //@Test public void substr_30()   { test("substr('👪', 1)",           
"'👪'"); }
+    @Test public void substr_30()   { test("substr('👪', 1)",           "'👪'"); 
}
+    // Written using \-u escapes in SPARQL. Tests for supplemental codepoint 
handling.
+    @Test public void substr_31()   { test("substr('\uD83D\uDC6A', 1)",        
   "'\uD83D\uDC6A'"); }
+    @Test public void substr_32()   { test("substr('\uD83D\uDC6A', 2)",        
   kwEmptyString); }
+    @Test public void substr_33()   { test("substr('ABC\uD83D\uDC6ADEF', 4, 
1)",  "'\uD83D\uDC6A'"); }
+    @Test public void substr_34()   { test("substr('\uD83D\uDC6A!', -1, 3)",   
   "'\uD83D\uDC6A'"); }
+    @Test public void substr_35()   { test("substr('\uD83D\uDC6A!', -1, 4)",   
   "'\uD83D\uDC6A!'"); }
 
-    // Written using \-u escapes in SPARQL.
-    @Test public void substr_30()   { test("substr('\\uD83D\\uDC6A', 1)",      
     "'\\uD83D\\uDC6A'"); }
-    // Same using Java string escapes.
-    @Test public void substr_30b()  { test("substr('\uD83D\uDC6A', 1)",        
     "'\uD83D\uDC6A'"); }
-    @Test public void substr_31()   { test("substr('\\uD83D\\uDC6A', 2)",      
     kwEmptyString); }
-
-    @Test public void substr_32()   { test("substr('ABC\\uD83D\\uDC6ADEF', 4, 
1)",  "'\\uD83D\\uDC6A'"); }
-    @Test public void substr_33()   { test("substr('\\uD83D\\uDC6A!', -1, 3)", 
     "'\\uD83D\\uDC6A'"); }
-    @Test public void substr_34()   { test("substr('\\uD83D\\uDC6A!', -1, 4)", 
     "'\\uD83D\\uDC6A!'"); }
+    // Now (1.2) numeric escape sequences do not allow surrogates. See 
TestQueryParser.
 
     // STRLEN
     @Test public void strlen_01()   { test("strlen('abc')",    "3"); }
@@ -701,10 +698,6 @@ public class TestSPARQLKeywordFunctions
 
     private void testEvalException(String exprStr) {
         Expr expr = ExprUtils.parse(exprStr);
-        try {
-            NodeValue r = expr.eval(null, LibTestExpr.createTest());
-            fail("No exception raised");
-        }
-        catch (ExprEvalException ex) {}
+        assertThrows(ExprEvalException.class,()->expr.eval(null, 
LibTestExpr.createTest()));
     }
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestQueryParser.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestQueryParser.java
index 8d16dd1b03..dd40139037 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestQueryParser.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/syntax/TestQueryParser.java
@@ -45,39 +45,51 @@ public class TestQueryParser {
         LogCtl.withLevel(loggerSPARQL, "fatal", action);
     }
 
-    // Single backslash so a Java string escape, raw surrogate in the string.
+    // These are  U+0001F46A 👪 - FAMILY
+    // As surrogate pair: 0xD83D 0xDC6A
+
+    @Test
+    public void syntax_unicode_raw_uri() {
+        testParse("SELECT * { <http://example/👪> ?p ?o}");
+    }
+
+    @Test
+    public void syntax_unicode_raw_string() {
+        testParse("SELECT * { ?s ?p '👪'}");
+    }
+
+    // Single backslash so a Java string escape, surrogate in the string.
     @Test
     public void syntax_unicode_raw_surrogate_uri() {
-        QueryParseException ex = assertThrows(QueryParseException.class,  
()->testParse("SELECT * { <http://example/\uD800> ?p ?o}"));
+        QueryParseException ex = assertThrows(QueryParseException.class, 
()->testParse("SELECT * { <http://example/\uD83D> ?p ?o}"));
         assertTrue(ex.getMessage().contains("surrogate"));
     }
 
     @Test
     public void syntax_unicode_raw_surrogate_string() {
-        QueryParseException ex = assertThrows(QueryParseException.class,  
()->testParse("SELECT * { ?s ?p '\uD800' }"));
+        QueryParseException ex = assertThrows(QueryParseException.class, 
()->testParse("SELECT * { ?s ?p '\uDC6A' }"));
         assertTrue(ex.getMessage().contains("surrogate"));
     }
 
     // Double backslash so the query string has an escape in it.
     @Test
     public void syntax_unicode_escaped_surrogate_uri() {
-        QueryParseException ex = assertThrows(QueryParseException.class,  
()->testParse("SELECT * { <http://example/\\uD800> ?p ?o}"));
+        QueryParseException ex = assertThrows(QueryParseException.class, 
()->testParse("SELECT * { <http://example/\\uD83D> ?p ?o}"));
         assertTrue(ex.getMessage().contains("surrogate"));
     }
 
     @Test
     public void syntax_unicode_escaped_surrogate_strings() {
-        QueryParseException ex = assertThrows(QueryParseException.class,  
()->testParse("SELECT * { ?s ?p '\\uD800'}"));
+        QueryParseException ex = assertThrows(QueryParseException.class, 
()->testParse("SELECT * { ?s ?p '\\uD83D'}"));
         assertTrue(ex.getMessage().contains("surrogate"));
     }
 
     @Test
     public void syntax_unicode_surrogate_pair_by_unicode_escape() {
-        // Allow - because Java strings may have surrogate pairs so we allow 
them in unicode escapes if paired.
-        testParse("SELECT * { ?s ?p '\\uD801\\uDC37'}");
-
-//        QueryParseException ex = assertThrows(QueryParseException.class,  
()->testParse("SELECT * { ?s ?p '\\uD801\\uDC37'}"));
-//        assertTrue(ex.getMessage().contains("surrogate"));
+//        // Allow - because Java strings may have surrogate pairs so we allow 
them in unicode escapes if paired.
+//        testParse("SELECT * { ?s ?p '\\uD801\\uDC37'}");
+        QueryParseException ex = assertThrows(QueryParseException.class,  
()->testParse("SELECT * { ?s ?p '\\uD801\\uDC37'}"));
+        assertTrue(ex.getMessage().contains("Surrogate"));
     }
 
     private static void testParse(String string) {
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java 
b/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java
index 5c2dde137d..61cf515af8 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java
@@ -21,6 +21,8 @@
 
 package org.apache.jena.atlas.lib;
 
+import static org.apache.jena.atlas.lib.Chars.isSurrogate;
+
 import org.apache.jena.atlas.AtlasException;
 import org.apache.jena.atlas.io.AWriter;
 import org.apache.jena.atlas.io.OutputUtils;
@@ -222,7 +224,7 @@ public class EscapeStr
             char ch = s.charAt(i);
 
             if ( ch != escape ) {
-                sb.append(ch);
+                insertCodepoint(sb, ch);
                 continue;
             }
 
@@ -244,7 +246,10 @@ public class EscapeStr
                 if ( i+4 >= s.length() )
                     throw new AtlasException("\\u escape too short");
                 int x4 = Hex.hexStringToInt(s, i+1, 4);
-                sb.append((char)x4);
+                if ( isSurrogate(x4) )
+                    throw new AtlasException(String.format("Surrogate code 
point in \\u sequence. Value: 0x%04X", x4));
+                // Do direct.
+                insertCodepoint(sb, (char)x4);
                 // Jump 1 2 3 4 -- already skipped \ and u
                 i = i+4;
                 continue;
@@ -253,19 +258,9 @@ public class EscapeStr
                 if ( i+8 >= s.length() )
                     throw new AtlasException("\\U escape too short");
                 int ch8 = Hex.hexStringToInt(s, i+1, 8);
-                if ( Character.charCount(ch8) == 1 )
-                    sb.append((char)ch8);
-                else {
-                    // See also TokenerText.insertCodepoint and 
TokenerText.readUnicodeEscape
-                    // Convert to UTF-16. Note that the rest of any system 
this is used
-                    // in must also respect codepoints and surrogate pairs.
-                    if ( !Character.isDefined(ch8) && 
!Character.isSupplementaryCodePoint(ch8) )
-                        throw new AtlasException(String.format("Illegal 
codepoint: 0x%04X", ch8));
-                    if ( ch8 > Character.MAX_CODE_POINT )
-                        throw new AtlasException(String.format("Illegal code 
point in \\U sequence value: 0x%08X", ch8));
-                    char[] chars = Character.toChars(ch8);
-                    sb.append(chars);
-                }
+                if ( isSurrogate(ch8) )
+                    throw new AtlasException(String.format("Surrogate code 
point in \\U sequence. Value: 0x%08X", ch8));
+                insertCodepoint(sb, ch8);
                 // Jump 1 2 3 4 5 6 7 8 -- already skipped \ and U
                 i = i+8;
                 continue;
@@ -275,8 +270,8 @@ public class EscapeStr
             // If so, \X-anything else is legal as a literal "\" and "X"
 
             if ( pointCodeOnly ) {
-                sb.append('\\');
-                sb.append(ch2);
+                insertCodepoint(sb, '\\');
+                insertCodepoint(sb, ch2);
                 continue;
             }
 
@@ -294,7 +289,7 @@ public class EscapeStr
             }
 
             if ( actualCh != 0 ) {
-                sb.append(actualCh);
+                insertCodepoint(sb, actualCh);
                 continue;
             }
 
@@ -336,20 +331,26 @@ public class EscapeStr
         }
         if ( j == 0 )
             throw new AtlasException("Empty \\u{} sequence");
+        if ( isSurrogate(value) )
+            throw new AtlasException(String.format("Surrogate code point in 
\\u{} sequence: 0x%08X", value));
+        insertCodepoint(sb, value);
+        // Looking at the closing '}'
+        i = i+j;
+        return i;
+    }
 
-        int ch8 = value;
-        if ( Character.charCount(ch8) == 1 )
-            sb.append((char)ch8);
+    private static void insertCodepoint(StringBuilder sb, int cp) {
+        if ( Character.charCount(cp) == 1 )
+            sb.append((char)cp);
         else {
-            if ( !Character.isDefined(ch8) && 
!Character.isSupplementaryCodePoint(ch8) )
-                throw new AtlasException(String.format("Illegal codepoint: 
0x%04X", ch8));
-            if ( ch8 > Character.MAX_CODE_POINT )
-                throw new AtlasException(String.format("Illegal code point in 
\\u{..} sequence value: 0x%08X", ch8));
-            char[] chars = Character.toChars(ch8);
+            if ( !Character.isDefined(cp) && 
!Character.isSupplementaryCodePoint(cp) )
+                throw new AtlasException(String.format("Illegal codepoint: 
0x%04X", cp));
+            char[] chars = Character.toChars(cp);
             sb.append(chars);
         }
-        // Looking at the closing '}'
-        i = i+j;
-        return i;
     }
-}
\ No newline at end of file
+
+    private static void insertCodepoint(StringBuilder sb, char cp) {
+        sb.append(cp);
+    }
+}
diff --git 
a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestEscapeStr.java 
b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestEscapeStr.java
index bac02e4dd5..95c525f3c2 100644
--- a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestEscapeStr.java
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestEscapeStr.java
@@ -26,7 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.StringWriter;
 
-import org.junit.jupiter.api.Test ;
+import org.junit.jupiter.api.Test;
 
 import org.apache.jena.atlas.AtlasException;
 import org.apache.jena.atlas.io.AWriter;
@@ -41,32 +41,32 @@ public class TestEscapeStr {
     private static char D_QUOTE = '"';
 
     // General, for double quoted strings.
-    @Test public void escape_str_01()   { test_esc("", "") ; }
-    @Test public void escape_str_02()   { test_esc("A", "A") ; }
-    @Test public void escape_str_03()   { test_esc("\n", "\\n") ; }
-    @Test public void escape_str_04()   { test_esc("A\tB", "A\\tB") ; }
-    @Test public void escape_str_05()   { test_esc("\"", "\\\"") ; }
-    @Test public void escape_str_06()   { test_esc("'", "'") ; }
+    @Test public void escape_str_01()   { test_esc("", ""); }
+    @Test public void escape_str_02()   { test_esc("A", "A"); }
+    @Test public void escape_str_03()   { test_esc("\n", "\\n"); }
+    @Test public void escape_str_04()   { test_esc("A\tB", "A\\tB"); }
+    @Test public void escape_str_05()   { test_esc("\"", "\\\""); }
+    @Test public void escape_str_06()   { test_esc("'", "'"); }
 
     private static void test_esc(String input, String expected) {
-        String output = EscapeStr.stringEsc(input) ;
+        String output = EscapeStr.stringEsc(input);
         assertEquals(expected, output, ()->"Failed at escape");
         String output2 = EscapeStr.unescapeStr(output);
         assertEquals(input, output2, ()->"Failed at unescape");
     }
 
     // Single line
-    @Test public void escape_str_single_01()   { test_esc1("a'x",  S_QUOTE,  
"a\\'x") ; }
-    @Test public void escape_str_single_02()   { test_esc1("a\"x", D_QUOTE,  
"a\\\"x") ; }
+    @Test public void escape_str_single_01()   { test_esc1("a'x",  S_QUOTE,  
"a\\'x"); }
+    @Test public void escape_str_single_02()   { test_esc1("a\"x", D_QUOTE,  
"a\\\"x"); }
 
-    @Test public void escape_str_single_03()   { test_esc1("a\"x", S_QUOTE,  
"a\"x") ; }
-    @Test public void escape_str_single_04()   { test_esc1("a'x",  D_QUOTE,  
"a'x") ; }
+    @Test public void escape_str_single_03()   { test_esc1("a\"x", S_QUOTE,  
"a\"x"); }
+    @Test public void escape_str_single_04()   { test_esc1("a'x",  D_QUOTE,  
"a'x"); }
 
-    @Test public void escape_str_single_05()   { test_esc1("a'",   S_QUOTE,  
"a\\'") ; }
-    @Test public void escape_str_single_06()   { test_esc1("a\"",  D_QUOTE,  
"a\\\"") ; }
+    @Test public void escape_str_single_05()   { test_esc1("a'",   S_QUOTE,  
"a\\'"); }
+    @Test public void escape_str_single_06()   { test_esc1("a\"",  D_QUOTE,  
"a\\\""); }
 
-    @Test public void escape_str_single_07()   { test_esc1("\"",  S_QUOTE,  
"\"") ; }
-    @Test public void escape_str_single_08()   { test_esc1("'",   D_QUOTE,  
"'") ; }
+    @Test public void escape_str_single_07()   { test_esc1("\"",  S_QUOTE,  
"\""); }
+    @Test public void escape_str_single_08()   { test_esc1("'",   D_QUOTE,  
"'"); }
 
     private static void test_esc1(String input, char quoteChar, String 
expected) {
         StringWriter sw = new StringWriter();
@@ -79,43 +79,43 @@ public class TestEscapeStr {
 
     // Multiline quoting.
     // One character
-    @Test public void escape_str_multi_01()    { test_esc3("a'x",  S_QUOTE,  
"a'x") ; }
-    @Test public void escape_str_multi_02()    { test_esc3("a\"x", D_QUOTE,  
"a\"x") ; }
+    @Test public void escape_str_multi_01()    { test_esc3("a'x",  S_QUOTE,  
"a'x"); }
+    @Test public void escape_str_multi_02()    { test_esc3("a\"x", D_QUOTE,  
"a\"x"); }
 
-    @Test public void escape_str_multi_03()    { test_esc3("'x", S_QUOTE, 
"'x") ; }
-    @Test public void escape_str_multi_04()    { test_esc3("'x", D_QUOTE, 
"'x") ; }
+    @Test public void escape_str_multi_03()    { test_esc3("'x", S_QUOTE, 
"'x"); }
+    @Test public void escape_str_multi_04()    { test_esc3("'x", D_QUOTE, 
"'x"); }
 
     // Last character
-    @Test public void escape_str_multi_05()    { test_esc3("a'", S_QUOTE, 
"a\\'") ; }
-    @Test public void escape_str_multi_06()    { test_esc3("a'", D_QUOTE, 
"a'") ; }
+    @Test public void escape_str_multi_05()    { test_esc3("a'", S_QUOTE, 
"a\\'"); }
+    @Test public void escape_str_multi_06()    { test_esc3("a'", D_QUOTE, 
"a'"); }
 
-    @Test public void escape_str_multi_07()    { test_esc3("a\"", S_QUOTE, 
"a\"") ; }
-    @Test public void escape_str_multi_08()    { test_esc3("a\"", D_QUOTE, 
"a\\\"") ; }
+    @Test public void escape_str_multi_07()    { test_esc3("a\"", S_QUOTE, 
"a\""); }
+    @Test public void escape_str_multi_08()    { test_esc3("a\"", D_QUOTE, 
"a\\\""); }
 
-    @Test public void escape_str_multi_09()    { test_esc3("'", S_QUOTE, 
"\\'") ; }
-    @Test public void escape_str_multi_10()    { test_esc3("'", D_QUOTE, "'") 
; }
+    @Test public void escape_str_multi_09()    { test_esc3("'", S_QUOTE, 
"\\'"); }
+    @Test public void escape_str_multi_10()    { test_esc3("'", D_QUOTE, "'"); 
}
 
-    @Test public void escape_str_multi_11()    { test_esc3("\"", S_QUOTE, 
"\"") ; }
-    @Test public void escape_str_multi_12()    { test_esc3("\"", D_QUOTE, 
"\\\"") ; }
+    @Test public void escape_str_multi_11()    { test_esc3("\"", S_QUOTE, 
"\""); }
+    @Test public void escape_str_multi_12()    { test_esc3("\"", D_QUOTE, 
"\\\""); }
 
     // 2 in a row
-    @Test public void escape_str_multi_2q_1()  { test_esc3("a''z", S_QUOTE, 
"a''z") ; }
-    @Test public void escape_str_multi_2q_2()  { test_esc3("a''z", D_QUOTE, 
"a''z") ; }
-    @Test public void escape_str_multi_2q_3()  { test_esc3("a''",  S_QUOTE, 
"a'\\'") ; }
-    @Test public void escape_str_multi_2q_4()  { test_esc3("a''",  D_QUOTE, 
"a''") ; }
+    @Test public void escape_str_multi_2q_1()  { test_esc3("a''z", S_QUOTE, 
"a''z"); }
+    @Test public void escape_str_multi_2q_2()  { test_esc3("a''z", D_QUOTE, 
"a''z"); }
+    @Test public void escape_str_multi_2q_3()  { test_esc3("a''",  S_QUOTE, 
"a'\\'"); }
+    @Test public void escape_str_multi_2q_4()  { test_esc3("a''",  D_QUOTE, 
"a''"); }
 
     // 3 in a row.
-    @Test public void escape_str_multi_3q_1()  { test_esc3("a'''z", S_QUOTE, 
"a''\\'z") ; }
-    @Test public void escape_str_multi_3q_2()  { test_esc3("a'''z", D_QUOTE, 
"a'''z") ; }
-    @Test public void escape_str_multi_3q_3()  { test_esc3("a'''",  S_QUOTE, 
"a''\\'") ; }
-    @Test public void escape_str_multi_3q_4()  { test_esc3("a'''",  D_QUOTE, 
"a'''") ; }
+    @Test public void escape_str_multi_3q_1()  { test_esc3("a'''z", S_QUOTE, 
"a''\\'z"); }
+    @Test public void escape_str_multi_3q_2()  { test_esc3("a'''z", D_QUOTE, 
"a'''z"); }
+    @Test public void escape_str_multi_3q_3()  { test_esc3("a'''",  S_QUOTE, 
"a''\\'"); }
+    @Test public void escape_str_multi_3q_4()  { test_esc3("a'''",  D_QUOTE, 
"a'''"); }
 
     // 4 in a row.
-    @Test public void escape_str_multi_4q_1()    { test_esc3("a''''z", 
S_QUOTE, "a''\\''z") ; }
-    @Test public void escape_str_multi_4q_2()    { test_esc3("a''''z", 
D_QUOTE, "a''''z") ; }
+    @Test public void escape_str_multi_4q_1()    { test_esc3("a''''z", 
S_QUOTE, "a''\\''z"); }
+    @Test public void escape_str_multi_4q_2()    { test_esc3("a''''z", 
D_QUOTE, "a''''z"); }
 
-    @Test public void escape_str_multi_4q_3()    { test_esc3("a''''", S_QUOTE, 
"a''\\'\\'") ; }
-    @Test public void escape_str_multi_4q_4()    { test_esc3("a''''", D_QUOTE, 
"a''''") ; }
+    @Test public void escape_str_multi_4q_3()    { test_esc3("a''''", S_QUOTE, 
"a''\\'\\'"); }
+    @Test public void escape_str_multi_4q_4()    { test_esc3("a''''", D_QUOTE, 
"a''''"); }
 
     // Unicode replacement char U+FFFD - write in Unicode escape form.
     @Test public void escape_str_repacementChar_1()    { 
test_esc1("abc\uFFFDdef", S_QUOTE, "abc\\uFFFDdef"); }
@@ -131,71 +131,92 @@ public class TestEscapeStr {
     }
 
     // Unescape
-    @Test public void unescape_str_10()   { test_unesc("\\u0041", "A") ; }
-    @Test public void unescape_str_11()   { test_unesc("\\U00000041", "A") ; }
-    @Test public void unescape_str_12()   { test_unesc("12\\u004134", "12A34") 
; }
-    @Test public void unescape_str_13()   { test_unesc("12\\U0000004134", 
"12A34") ; }
+    @Test public void unescape_str_10()   { test_unesc("\\u0041", "A"); }
+    @Test public void unescape_str_11()   { test_unesc("\\U00000041", "A"); }
+    @Test public void unescape_str_12()   { test_unesc("12\\u004134", 
"12A34"); }
+    @Test public void unescape_str_13()   { test_unesc("12\\U0000004134", 
"12A34"); }
 
     private void test_unesc(String input, String expected) {
-        String output = EscapeStr.unescapeStr(input) ;
+        String output = EscapeStr.unescapeStr(input);
         assertEquals(expected, output);
     }
 
-    private void test_escape(String input, String expected) {
+    private void test_escape_string(String input, String expected) {
         String output = EscapeStr.stringEsc(input);
         assertEquals(expected, output);
     }
-    @Test public void unescape_unicode_1()   { test_unesc_unicode("", "") ; }
-    @Test public void unescape_unicode_2()   { 
test_unesc_unicode("abc\\u0020def", "abc def") ; }
-    @Test public void unescape_unicode_3()   { test_unesc_unicode("\\u0020", " 
") ; }
-    @Test public void unescape_unicode_4()   { 
test_unesc_unicode("abc\\U00000020def", "abc def") ; }
-    @Test public void unescape_unicode_5()   { 
test_unesc_unicode("\\U00000020", " ") ; }
+
+    @Test public void unescape_unicode_1()   { test_unesc_unicode("", ""); }
+    @Test public void unescape_unicode_2()   { 
test_unesc_unicode("abc\\u0020def", "abc def"); }
+    @Test public void unescape_unicode_3()   { test_unesc_unicode("\\u0020", " 
"); }
+    @Test public void unescape_unicode_4()   { 
test_unesc_unicode("abc\\U00000020def", "abc def"); }
+    @Test public void unescape_unicode_5()   { 
test_unesc_unicode("\\U00000020", " "); }
 
     // Leaves non-unicode untouched.
-    @Test public void unescape_unicode_10()   { test_unesc_unicode("\\1\\2", 
"\\1\\2") ; }
-    @Test public void unescape_unicode_11()   { test_unesc_unicode("\\n\\t", 
"\\n\\t") ; }
-    @Test public void unescape_unicode_12()   { test_unesc_unicode("\\(\\)", 
"\\(\\)") ; }
-    @Test public void unescape_unicode_13()   { test_unesc_unicode("\\\\", 
"\\\\") ; }
+    @Test public void unescape_unicode_10()   { test_unesc_unicode("\\1\\2", 
"\\1\\2"); }
+    @Test public void unescape_unicode_11()   { test_unesc_unicode("\\n\\t", 
"\\n\\t"); }
+    @Test public void unescape_unicode_12()   { test_unesc_unicode("\\(\\)", 
"\\(\\)"); }
+    @Test public void unescape_unicode_13()   { test_unesc_unicode("\\\\", 
"\\\\"); }
 
     // \-u{...} style Unicode escapes
-    @Test public void unescape_unicode_20()   { test_unesc_unicode("\\u{41}", 
"A") ; }
-    @Test public void unescape_unicode_21()   { 
test_unesc_unicode("\\u{000000}", "\u0000") ; }
-    @Test public void unescape_unicode_22()   { 
test_unesc_unicode("\\u{1F0A1}", "🂡") ; }
-    @Test public void unescape_unicode_23()   { 
test_unesc_unicode("\\u{01F0A1}", "🂡") ; }
-    @Test public void unescape_unicode_24()   { 
test_unesc_unicode("\\u{10FFFF}", 0x10FFFF) ; }
-
-    @Test public void unescape_unicode_30()   { 
assertThrows(AtlasException.class, ()->test_unesc_unicode("\\u{}", "")) ; }
-    @Test public void unescape_unicode_31()   { 
assertThrows(AtlasException.class, ()->test_unesc_unicode("\\u{123456789}", 
"")) ; }
-    @Test public void unescape_unicode_32()   { 
assertThrows(AtlasException.class, ()->test_unesc_unicode("\\u{000000000}", 
"")) ; }
+    @Test public void unescape_unicode_20()   { test_unesc_unicode("\\u{41}", 
"A"); }
+    @Test public void unescape_unicode_21()   { 
test_unesc_unicode("\\u{000000}", "\u0000"); }
+    @Test public void unescape_unicode_22()   { 
test_unesc_unicode("\\u{1F0A1}", "🂡"); }
+    @Test public void unescape_unicode_23()   { 
test_unesc_unicode("\\u{01F0A1}", "🂡"); }
+    @Test public void unescape_unicode_24()   { 
test_unesc_unicode("\\u{10FFFF}", 0x10FFFF); }
+
+    @Test public void unescape_unicode_30()   { 
test_unesc_unicode_bad("\\u{}"); }
+    @Test public void unescape_unicode_31()   { 
test_unesc_unicode_bad("\\u{123456789}"); }
+    @Test public void unescape_unicode_32()   { 
test_unesc_unicode_bad("\\u{000000000}"); }
     // If the limit is 6
-    @Test public void unescape_unicode_33()   { 
assertThrows(AtlasException.class, ()->test_unesc_unicode("\\u{1234567}", "")) 
; }
-    @Test public void unescape_unicode_34()   { 
assertThrows(AtlasException.class, ()->test_unesc_unicode("\\u{0000000}", "")) 
; }
+    @Test public void unescape_unicode_33()   { 
test_unesc_unicode_bad("\\u{1234567}"); }
+    @Test public void unescape_unicode_34()   { 
test_unesc_unicode_bad("\\u{0000000}"); }
+
+    // Surrogates via \-u, \-U and \-u{}
+    @Test public void unescape_unicode_40()   { 
test_unesc_unicode_bad("\\uD83C\\uDCA1"); }
+    @Test public void unescape_unicode_41()   { 
test_unesc_unicode_bad("\\U0000D83C\\U0000DCA1"); }
+    @Test public void unescape_unicode_42()   { 
test_unesc_unicode_bad("\\u{00D83C}\\u{00DCA1}"); }
+
+    // Not valid surrogate pairs.
+    @Test public void unescape_unicode_50()   { 
test_unesc_unicode_bad("\\uD83Cxyz"); }
+    @Test public void unescape_unicode_51()   { 
test_unesc_unicode_bad("\\U0000D83Cxyz"); }
+    @Test public void unescape_unicode_52()   { 
test_unesc_unicode_bad("\\u{00D83C}xyz"); }
+
+    @Test public void unescape_unicode_60()   { 
test_unesc_unicode_bad("abc\\uDCA1xyz"); }
+    @Test public void unescape_unicode_61()   { 
test_unesc_unicode_bad("abc\\U0000DCA1xyz"); }
+    @Test public void unescape_unicode_62()   { 
test_unesc_unicode_bad("abc\\u{00DCA1}xyz"); }
 
     // Escaped surrogates, good and bad.
     // Use java character escapes to put the surrogates into the java string.
     // 🂡 is U+D83C U+DCA1
-    @Test public void escape_unicode_50()   { test_escape("\uD83C\uDCA1", 
"🂡"); }
-    @Test public void escape_unicode_51()   { 
test_escape("abc\uD83C\uDCA1xyz", "abc🂡xyz"); }
+    @Test public void escape_unicode_70()   { 
test_escape_string("\uD83C\uDCA1", "🂡"); }
+    @Test public void escape_unicode_71()   { 
test_escape_string("abc\uD83C\uDCA1xyz", "abc🂡xyz"); }
     // low, then high -> illegal
-    @Test public void escape_unicode_55()   { test_escape("\uDCA1\uD83C", 
"\\uFFFD\\uFFFD"); }
-    @Test public void escape_unicode_56()   { test_escape("\uDCA1\uD83C@", 
"\\uFFFD\\uFFFD@"); }
+    @Test public void escape_unicode_75()   { 
test_escape_string("\uDCA1\uD83C", "\\uFFFD\\uFFFD"); }
+    @Test public void escape_unicode_76()   { 
test_escape_string("\uDCA1\uD83C@", "\\uFFFD\\uFFFD@"); }
     // Lone surrogate
-    @Test public void escape_unicode_60()   { test_escape("\uD83C", 
"\\uFFFD"); }
-    @Test public void escape_unicode_61()   { test_escape("abc\uD83Cxyz", 
"abc\\uFFFDxyz"); }
-    @Test public void escape_unicode_62()   { test_escape("\uDCA1", 
"\\uFFFD"); }
-    @Test public void escape_unicode_63()   { test_escape("abc\uDCA1xyz", 
"abc\\uFFFDxyz"); }
+    @Test public void escape_unicode_80()   { test_escape_string("\uD83C", 
"\\uFFFD"); }
+    @Test public void escape_unicode_81()   { 
test_escape_string("abc\uD83Cxyz", "abc\\uFFFDxyz"); }
+    @Test public void escape_unicode_82()   { test_escape_string("\uDCA1", 
"\\uFFFD"); }
+    @Test public void escape_unicode_83()   { 
test_escape_string("abc\uDCA1xyz", "abc\\uFFFDxyz"); }
+    // Illegal then legal
+    @Test public void escape_unicode_84()   { 
test_escape_string("\uDCA1\uD83C\uDCA1", "\\uFFFD🂡"); }
+
 
     // low, then high/low -> one illegal, encode legal pair.
-    @Test public void escape_unicode_59()   { 
test_escape("\uDCA1\uD83C\uDCA1", "\\uFFFD🂡"); }
 
     private void test_unesc_unicode(String input, String expected) {
-        String output = EscapeStr.unescapeUnicode(input) ;
+        String output = EscapeStr.unescapeUnicode(input);
         assertEquals(expected, output);
     }
 
     private void test_unesc_unicode(String input, int expected) {
-        String output = EscapeStr.unescapeUnicode(input) ;
+        String output = EscapeStr.unescapeUnicode(input);
         int codepoint = output.codePointAt(0);
         assertEquals(expected, codepoint);
     }
+
+    private void test_unesc_unicode_bad(String input) {
+        assertThrows(AtlasException.class, 
()->EscapeStr.unescapeUnicode(input));
+    }
 }

Reply via email to