Regex.cpp (9463B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Public/winget/Regex.h" 5 #include "Public/AppInstallerErrors.h" 6 #include "Public/AppInstallerLogging.h" 7 #include "Public/AppInstallerLanguageUtilities.h" 8 9 #define WINGET_THROW_REGEX_ERROR_IF_FAILED(_err_,_func_) \ 10 if (U_FAILURE(_err_)) \ 11 { \ 12 AICLI_LOG(Core, Error, << #_func_ " returned " << _err_); \ 13 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR); \ 14 } 15 16 17 namespace AppInstaller::Regex 18 { 19 struct Expression::impl 20 { 21 using uregex_ptr = wil::unique_any<URegularExpression*, decltype(uregex_close), uregex_close>; 22 using utext_ptr = wil::unique_any<UText*, decltype(utext_close), utext_close>; 23 24 // Create caches the original ICU regex objects in a static map and hands out copies of them 25 // when requested. Since we have a limited set, this is a very simple cache-all-forever pattern. 26 static std::unique_ptr<impl> Create(std::string_view pattern, Options options) 27 { 28 struct key 29 { 30 std::string pattern; 31 Options options = Options::None; 32 33 bool operator<(const key& other) const 34 { 35 if (pattern < other.pattern) 36 { 37 return true; 38 } 39 else if (pattern == other.pattern) 40 { 41 return ToIntegral(options) < ToIntegral(other.options); 42 } 43 else 44 { 45 return false; 46 } 47 } 48 }; 49 50 struct statics 51 { 52 std::map<key, impl> map; 53 wil::srwlock lock; 54 }; 55 56 static statics s_regex_cache; 57 58 key requested; 59 requested.pattern = pattern; 60 requested.options = options; 61 62 { 63 // Attempt to find in the cache 64 auto sharedLock = s_regex_cache.lock.lock_shared(); 65 66 auto itr = s_regex_cache.map.find(requested); 67 if (itr != s_regex_cache.map.end()) 68 { 69 return std::make_unique<impl>(itr->second); 70 } 71 } 72 73 auto exclusiveLock = s_regex_cache.lock.lock_exclusive(); 74 75 // Check if another thread created it while we waited for the lock. 76 auto itr = s_regex_cache.map.find(requested); 77 if (itr != s_regex_cache.map.end()) 78 { 79 return std::make_unique<impl>(itr->second); 80 } 81 else 82 { 83 return std::make_unique<impl>(s_regex_cache.map.emplace(std::move(requested), impl{ pattern, options }).first->second); 84 } 85 } 86 87 impl(std::string_view pattern, Options options) 88 { 89 UErrorCode uec = U_ZERO_ERROR; 90 91 utext_ptr patternUtext{ utext_openUTF8(nullptr, pattern.data(), pattern.length(), &uec) }; 92 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_openUTF8); 93 94 // For now, just handle the one option 95 uint32_t flags = 0; 96 97 if (options == Options::CaseInsensitive) 98 { 99 flags = UREGEX_CASE_INSENSITIVE; 100 } 101 102 UParseError parseError{}; 103 104 m_regex.reset(uregex_openUText(patternUtext.get(), flags, &parseError, &uec)); 105 106 if (U_FAILURE(uec)) 107 { 108 AICLI_LOG(Core, Error, << "uregex_openUText failed with error [" << uec << "] at line " << parseError.line << ", position " << parseError.offset << '\n' << pattern); 109 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR); 110 } 111 } 112 113 impl(const impl& other) 114 { 115 UErrorCode uec = U_ZERO_ERROR; 116 117 m_regex.reset(uregex_clone(other.m_regex.get(), &uec)); 118 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_clone); 119 } 120 121 impl& operator=(const impl& other) 122 { 123 *this = impl{ other }; 124 return *this; 125 } 126 127 impl(impl&&) = default; 128 impl& operator=(impl&&) = default; 129 130 ~impl() = default; 131 132 bool IsMatch(std::wstring_view input) const 133 { 134 UErrorCode uec = U_ZERO_ERROR; 135 136 SetText(input); 137 138 UBool result = uregex_matches(m_regex.get(), -1, &uec); 139 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_matches); 140 141 return !!result; 142 } 143 144 std::wstring Replace(std::wstring_view input, std::wstring_view replacement) const 145 { 146 UErrorCode uec = U_ZERO_ERROR; 147 148 SetText(input); 149 150 std::u16string_view u16replacement = Convert(replacement); 151 utext_ptr replacementUtext{ utext_openUChars(nullptr, u16replacement.data(), u16replacement.length(), &uec) }; 152 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_openUTF8); 153 154 utext_ptr resultUText{ uregex_replaceAllUText(m_regex.get(), replacementUtext.get(), nullptr, &uec) }; 155 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_replaceAllUText); 156 157 int64_t cch = utext_nativeLength(resultUText.get()); 158 std::wstring result(static_cast<size_t>(cch), '\0'); 159 160 utext_extract(resultUText.get(), 0, std::numeric_limits<int64_t>::max(), reinterpret_cast<char16_t*>(&result[0]), static_cast<int32_t>(result.size()), &uec); 161 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_extract); 162 163 return result; 164 } 165 166 void ForEach(std::wstring_view input, const std::function<bool(bool, std::wstring_view)>&f) const 167 { 168 UErrorCode uec = U_ZERO_ERROR; 169 170 SetText(input); 171 int32_t startPos = 0; 172 173 while (uregex_findNext(m_regex.get(), &uec)) 174 { 175 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_findNext); 176 177 int32_t pos = uregex_start(m_regex.get(), 0, &uec); 178 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_start); 179 THROW_HR_IF(E_UNEXPECTED, pos == -1); 180 181 // First, send off the unmatched part before the match 182 if (pos > startPos) 183 { 184 if (!f(false, input.substr(startPos, static_cast<size_t>(pos) - startPos))) 185 { 186 return; 187 } 188 } 189 190 // Now send the matched part 191 int32_t end = uregex_end(m_regex.get(), 0, &uec); 192 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_end); 193 THROW_HR_IF(E_UNEXPECTED, end == -1); 194 195 if (!f(true, input.substr(pos, static_cast<size_t>(end) - pos))) 196 { 197 return; 198 } 199 200 startPos = end; 201 } 202 203 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_findNext); 204 205 // Finally, send any remaining part 206 if (input.length() > static_cast<size_t>(startPos)) 207 { 208 f(false, input.substr(startPos)); 209 } 210 } 211 212 private: 213 static std::u16string_view Convert(std::wstring_view input) 214 { 215 static_assert(sizeof(wchar_t) == sizeof(char16_t), "wchar_t and char16_t must be the same size"); 216 return { reinterpret_cast<const char16_t*>(input.data()), input.size() }; 217 } 218 219 void SetText(std::wstring_view input) const 220 { 221 UErrorCode uec = U_ZERO_ERROR; 222 223 std::u16string_view u16 = Convert(input); 224 225 uregex_setText(m_regex.get(), u16.data(), static_cast<int32_t>(u16.length()), &uec); 226 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_setText); 227 } 228 229 uregex_ptr m_regex; 230 }; 231 232 Expression::Expression() = default; 233 234 Expression::Expression(std::string_view pattern, Options options) : pImpl(impl::Create(pattern, options)) {} 235 236 Expression::Expression(const Expression& other) 237 { 238 if (other.pImpl) 239 { 240 pImpl = std::make_unique<impl>(*other.pImpl); 241 } 242 } 243 244 Expression& Expression::operator=(const Expression& other) 245 { 246 return *this = Expression{ other }; 247 } 248 249 Expression::Expression(Expression&&) noexcept = default; 250 Expression& Expression::operator=(Expression&&) noexcept = default; 251 252 Expression::~Expression() = default; 253 254 Expression::operator bool() const 255 { 256 return static_cast<bool>(pImpl); 257 } 258 259 bool Expression::IsMatch(std::wstring_view input) const 260 { 261 THROW_HR_IF(E_NOT_VALID_STATE, !pImpl); 262 return pImpl->IsMatch(input); 263 } 264 265 std::wstring Expression::Replace(std::wstring_view input, std::wstring_view replacement) const 266 { 267 THROW_HR_IF(E_NOT_VALID_STATE, !pImpl); 268 return pImpl->Replace(input, replacement); 269 } 270 271 void Expression::ForEach(std::wstring_view input, const std::function<bool(bool, std::wstring_view)>& f) const 272 { 273 THROW_HR_IF(E_NOT_VALID_STATE, !pImpl); 274 return pImpl->ForEach(input, f); 275 } 276 }