java - Java中模式匹配中的Stackoverflow

标签 java regex regex-greedy

我试图根据未包含在双引号之间的空格来拆分一行。

我的正则表达式是

(([\"]([^\\\"]|\\.)+[\"]|[^ ]+))+

我的代码
Pattern regex          = Pattern.compile("(([\"]([^\\\"]|\\.)+[\"]|[^ ]+))+");
Matcher regexMatcher   = regex.matcher(line);
List<String> rule      = new ArrayList<String>();

while(regexMatcher.find())
    rule.add(regexMatcher.group());

失败的输入。
SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|ARGS_NAMES|ARGS|XML:/* "(?i:\b(?:(?:s(?:t(?:d(?:dev(_pop|_samp)?)?|r(?:_to_date|cmp))|u(?:b(?:str(?:ing(_index)?)?|(?:dat|tim)e)|m)|e(?:c(?:_to_time|ond)|ssion_user)|ys(?:tem_user|date)|ha(1|2)?|oundex|chema|ig?n|pace|qrt)|i(?:s(null|_(free_lock|ipv4_compat|ipv4_mapped|ipv4|ipv6|not_null|not|null|used_lock))?|n(?:et6?_(aton|ntoa)|s(?:ert|tr)|terval)?|f(null)?)|u(?:n(?:compress(?:ed_length)?|ix_timestamp|hex)|tc_(date|time|timestamp)|p(?:datexml|per)|uid(_short)?|case|ser)|l(?:o(?:ca(?:l(timestamp)?|te)|g(2|10)?|ad_file|wer)|ast(_day|_insert_id)?|e(?:(?:as|f)t|ngth)|case|trim|pad|n)|t(?:ime(stamp|stampadd|stampdiff|diff|_format|_to_sec)?|o_(base64|days|seconds|n?char)|r(?:uncate|im)|an)|m(?:a(?:ke(?:_set|date)|ster_pos_wait|x)|i(?:(?:crosecon)?d|n(?:ute)?)|o(?:nth(name)?|d)|d5)|r(?:e(?:p(?:lace|eat)|lease_lock|verse)|o(?:w_count|und)|a(?:dians|nd)|ight|trim|pad)|f(?:i(?:eld(_in_set)?|nd_in_set)|rom_(base64|days|unixtime)|o(?:und_rows|rmat)|loor)|a(?:es_(?:de|en)crypt|s(?:cii(str)?|in)|dd(?:dat|tim)e|(?:co|b)s|tan2?|vg)|p(?:o(?:sition|w(er)?)|eriod_(add|diff)|rocedure_analyse|assword|i)|b(?:i(?:t_(?:length|count|x?or|and)|n(_to_num)?)|enchmark)|e(?:x(?:p(?:ort_set)?|tract(value)?)|nc(?:rypt|ode)|lt)|v(?:a(?:r(?:_(?:sam|po)p|iance)|lues)|ersion)|g(?:r(?:oup_conca|eates)t|et_(format|lock))|o(?:(?:ld_passwo)?rd|ct(et_length)?)|we(?:ek(day|ofyear)?|ight_string)|n(?:o(?:t_in|w)|ame_const|ullif)|(rawton?)?hex(toraw)?|qu(?:arter|ote)|(pg_)?sleep|year(week)?|d?count|xmltype|hour)\W*\(|\b(?:(?:s(?:elect\b(?:.{1,100}?\b(?:(?:length|count|top)\b.{1,100}?\bfrom|from\b.{1,100}?\bwhere)|.*?\b(?:d(?:ump\b.*\bfrom|ata_type)|(?:to_(?:numbe|cha)|inst)r))|p_(?:sqlexec|sp_replwritetovarbin|sp_help|addextendedproc|is_srvrolemember|prepare|sp_password|execute(?:sql)?|makewebtask|oacreate)|ql_(?:longvarchar|variant))|xp_(?:reg(?:re(?:movemultistring|ad)|delete(?:value|key)|enum(?:value|key)s|addmultistring|write)|terminate|xp_servicecontrol|xp_ntsec_enumdomains|xp_terminate_process|e(?:xecresultset|numdsn)|availablemedia|loginconfig|cmdshell|filelist|dirtree|makecab|ntsec)|u(?:nion\b.{1,100}?\bselect|tl_(?:file|http))|d(?:b(?:a_users|ms_java)|elete\b\W*?\bfrom)|group\b.*\bby\b.{1,100}?\bhaving|open(?:rowset|owa_util|query)|load\b\W*?\bdata\b.*\binfile|(?:n?varcha|tbcreato)r|autonomous_transaction)\b|i(?:n(?:to\b\W*?\b(?:dump|out)file|sert\b\W*?\binto|ner\b\W*?\bjoin)\b|(?:f(?:\b\W*?\(\W*?\bbenchmark|null\b)|snull\b)\W*?\()|print\b\W*?\@\@|cast\b\W*?\()|c(?:(?:ur(?:rent_(?:time(?:stamp)?|date|user)|(?:dat|tim)e)|h(?:ar(?:(?:acter)?_length|set)?|r)|iel(?:ing)?|ast|r32)\W*\(|o(?:(?:n(?:v(?:ert(?:_tz)?)?|cat(?:_ws)?|nection_id)|(?:mpres)?s|ercibility|alesce|t)\W*\(|llation\W*\(a))|d(?:(?:a(?:t(?:e(?:(_(add|format|sub))?|diff)|abase)|y(name|ofmonth|ofweek|ofyear)?)|e(?:(?:s_(de|en)cryp|faul)t|grees|code)|ump)\W*\(|bms_\w+\.\b)|(?:;\W*?\b(?:shutdown|drop)|\@\@version)\b|\butl_inaddr\b|\bsys_context\b|'(?:s(?:qloledb|a)|msdasql|dbo)'))" "phase:2,rev:'2',ver:'OWASP_CRS/2.2.9',maturity:'9',accuracy:'8',capture,t:none,t:urlDecodeUni,ctl:auditLogParts=+E,block,msg:'SQL Injection Attack',id:'950001',tag:'OWASP_CRS/WEB_ATTACK/SQL_INJECTION',tag:'WASCTC/WASC-19',tag:'OWASP_TOP_10/A1',tag:'OWASP_AppSensor/CIE1',tag:'PCI/6.5.2',logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',severity:'2',setvar:'tx.msg=%{rule.msg}',setvar:tx.sql_injection_score=+%{tx.critical_anomaly_score},setvar:tx.anomaly_score=+%{tx.critical_anomaly_score},setvar:tx.%{rule.id}-OWASP_CRS/WEB_ATTACK/SQL_INJECTION-%{matched_var_name}=%{tx.0}
当我在java中使用它时,有些行被成功分离,但有些行导致错误
Exception in thread "main" java.lang.StackOverflowError
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4235)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4095)
at java.util.regex.Pattern$CharProperty.match(Pattern.java:3362)
at java.util.regex.Pattern$Branch.match(Pattern.java:4131)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4185)
at java.util.regex.Pattern$Loop.match(Pattern.java:4312)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4244)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4095)
at java.util.regex.Pattern$CharProperty.match(Pattern.java:3362)
at java.util.regex.Pattern$Branch.match(Pattern.java:4131)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4185)
at java.util.regex.Pattern$Loop.match(Pattern.java:4312)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4244)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4095)
at java.util.regex.Pattern$CharProperty.match(Pattern.java:3362)
at java.util.regex.Pattern$Branch.match(Pattern.java:4131)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4185)

样本输入:

The "world \"is beautiful" but i "cannot see" it



预期输出:
The
"world \" beautiful"
but
i
"cannot see"
it

最佳答案

为什么会发生 StackOverflowError?

Pattern 的引用实现中类(Oracle 的 JRE、OpenJDK 和许多其他 JVM 附带)、贪婪和惰性量词在重复模式不平凡时使用 recursion1 实现。因此,您将遇到 StackOverflowError当输入字符串足够长时。

1 递归是一种快速但不可扩展的解决方案,允许在模式中回溯。更好的实现使用数据结构来存储回溯点
(它基本上将递归解决方案转换为带有堆栈的迭代解决方案)。

解决方案

以下正则表达式应该可以工作:

"(?:\"(?:[^\"\\\\]++|\\\\.)*+\"|[^ \"]++)++"

好吧,正则表达式与 2 层转义非常混淆:Java 字符串文字中的转义和正则表达式语法中的转义。

打印字符串时的原始正则表达式。我的解释将基于原始正则表达式。
(?:"(?:[^"\\]++|\\.)*+"|[^ "]++)++

解释

由于您只关心整个正则表达式匹配的内容,因此所有捕获组 (pattern)已转为非捕获组(?:pattern)为了效率。

第一种选择"(?:[^"\\]++|\\.)*+"匹配带引号的字符串。

第二种选择[^ "]++匹配不包含空格和双引号的字符序列 " .
(?:
   "             # Double quote
   (?:
      [^"\\]++   # A sequence of characters that are not " and \
      |          # OR
      \\.        # Escape sequence: \ followed by any character (except line terminators)
   )*+           # Match 0 or more of the sequences above (allows empty string)
   "             # Double quote
   |
   [^ "]++
)++

由于编写正则表达式不需要回溯,因此所有量词都具有所有格。由于Pattern类通过循环实现所有格量词,而不是像贪婪/懒惰量词那样递归,StackOverflowError不会发生。

我通过编写正则表达式消除了回溯的需要,以便它在第一次尝试时匹配正确的字符串:
  • 由于[^"\\]不包括 \ ,我们无法“窃取” \从转义序列中,或“窃取” "并弄乱了收盘价,我们可以安全地前进而不会回溯。这解释了所有格量词 [^"\\]++ .这里不需要指定量词,但我这样做是为了减少分支上的工作。
  • 由于[^"\\]++\\.不能“偷”一个 "并弄乱了收盘价,我们可以向前推进而不回溯。这解释了所有格量词 (?:[^"\\]++|\\.)*+
  • [^ "]不能开始一个带引号的字符串,它也不能匹配一个空格(分隔符)。这就是为什么我们可以在其上使用所有格量词。
  • 由于"(?:[^"\\]++|\\.)*+"[^ "]++不能弄乱彼此的匹配,我们可以使最外层的量词具有所有格。

  • 正则表达式在第一次尝试时无法正确匹配并且仅在回溯后才获得正确结果的示例将是 ^([bcd]+:[ab]+)+$针对 b:ab:a 等输入.第一次迭代将匹配 b:ab ,这会导致第二次迭代失败,然后它回溯并重试第一次迭代为 b:a然后成功匹配整个字符串。

    关于java - Java中模式匹配中的Stackoverflow,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21701636/

    相关文章:

    java - 正则表达式多行html代码?

    regex - Eclipse 正则表达式搜索不起作用

    javascript - 查找 pec+ 的正则表达式模式

    java - 从实现类中获取接口(interface)名称

    java - 特定正则表达式如何解析嵌套 html 标签中的内容,Java

    JAVA - swing - 使用目标 Canvas 的反转/否定颜色进行绘制/填充

    java - 正则表达式使用空格分割字符串,但不考虑双引号或单引号

    regex - sed 中的非贪婪(不情愿)正则表达式匹配?

    java - 如何将多个字符串传递给另一个 Activity ?

    java - 如何格式化导出 pdf 中的显示标签总值行?