|
| 1 | +/** Provides decoded values of Swift string literals. */ |
| 2 | +overlay[local?] |
| 3 | +module; |
| 4 | + |
| 5 | +private import unified |
| 6 | +private import codeql.util.Numbers |
| 7 | + |
| 8 | +private predicate literalContents(StringLiteral literal, string text, string escapePrefix) { |
| 9 | + exists(string raw, string hashes, string quote, string contents | |
| 10 | + raw = literal.getValue().replaceAll("\r\n", "\n").replaceAll("\r", "\n") and |
| 11 | + hashes = raw.regexpCapture("(?s)(#*)(\"\"\"|\")(.*)\\2\\1", 1) and |
| 12 | + quote = raw.regexpCapture("(?s)(#*)(\"\"\"|\")(.*)\\2\\1", 2) and |
| 13 | + contents = raw.regexpCapture("(?s)(#*)(\"\"\"|\")(.*)\\2\\1", 3) and |
| 14 | + escapePrefix = "\\" + hashes |
| 15 | + | |
| 16 | + quote = "\"" and text = contents |
| 17 | + or |
| 18 | + quote = "\"\"\"" and |
| 19 | + exists(string body, string indent | |
| 20 | + body = contents.regexpCapture("(?s)\n(.*)\n([ \t]*)", 1) and |
| 21 | + indent = contents.regexpCapture("(?s)\n(.*)\n([ \t]*)", 2) and |
| 22 | + text = body.regexpReplaceAll("(?m)^" + indent, "") |
| 23 | + ) |
| 24 | + ) |
| 25 | +} |
| 26 | + |
| 27 | +private predicate decodedPart(StringLiteral literal, int start, int end, string value) { |
| 28 | + exists(string text, string prefix | |
| 29 | + literalContents(literal, text, prefix) and |
| 30 | + start in [0 .. text.length() - 1] |
| 31 | + | |
| 32 | + not text.substring(start, start + prefix.length()) = prefix and |
| 33 | + end = start + 1 and |
| 34 | + value = text.charAt(start) |
| 35 | + or |
| 36 | + text.substring(start, start + prefix.length()) = prefix and |
| 37 | + exists(int next | next = start + prefix.length() | |
| 38 | + end = next + 1 and |
| 39 | + ( |
| 40 | + text.charAt(next) = "0" and value = 0.toUnicode() |
| 41 | + or |
| 42 | + text.charAt(next) = "t" and value = "\t" |
| 43 | + or |
| 44 | + text.charAt(next) = "n" and value = "\n" |
| 45 | + or |
| 46 | + text.charAt(next) = "r" and value = "\r" |
| 47 | + or |
| 48 | + value = text.charAt(next) and value = ["\\", "\"", "'"] |
| 49 | + ) |
| 50 | + or |
| 51 | + exists(string hex | |
| 52 | + hex = |
| 53 | + text.substring(next, text.length()).regexpCapture("(?s)u\\{([0-9a-fA-F]{1,8})\\}.*", 1) and |
| 54 | + end = next + hex.length() + 3 and |
| 55 | + value = parseHexInt(hex).toUnicode() |
| 56 | + ) |
| 57 | + or |
| 58 | + exists(string continuation | |
| 59 | + continuation = text.substring(next, text.length()).regexpCapture("(?s)([ \t]*\n).*", 1) and |
| 60 | + end = next + continuation.length() and |
| 61 | + value = "" |
| 62 | + ) |
| 63 | + ) |
| 64 | + ) |
| 65 | +} |
| 66 | + |
| 67 | +private predicate decodedOffset(StringLiteral literal, int offset) { |
| 68 | + offset = 0 |
| 69 | + or |
| 70 | + decodedPart(literal, _, offset, _) and |
| 71 | + exists(int previous | |
| 72 | + decodedOffset(literal, previous) and |
| 73 | + decodedPart(literal, previous, offset, _) |
| 74 | + ) |
| 75 | +} |
| 76 | + |
| 77 | +/** Gets the decoded value of `literal`, if it is a complete, valid string literal. */ |
| 78 | +string getStringValue(StringLiteral literal) { |
| 79 | + exists(string text | |
| 80 | + literalContents(literal, text, _) and |
| 81 | + decodedOffset(literal, text.length()) |
| 82 | + | |
| 83 | + result = |
| 84 | + concat(int start, int end, string value | |
| 85 | + decodedOffset(literal, start) and decodedPart(literal, start, end, value) |
| 86 | + | |
| 87 | + value order by start |
| 88 | + ) |
| 89 | + ) |
| 90 | +} |
0 commit comments