正则匹配失败PREG_JIT_STACKLIMIT_ERROR

Posted by

最近项目中的配置因为条目太多,所以打算将.env配置文件拆分成多个文件,按照配置功能拆分。当前项目使用的配置组件是 -> “vlucas/phpdotenv”: “^5.1.0″,顺着源码看了下,配置加载如下:

  /**
     * Create a new dotenv instance.
     *
     * @param \Dotenv\Repository\RepositoryInterface $repository
     * @param string|string[]                        $paths
     * @param string|string[]|null                   $names
     * @param bool                                   $shortCircuit
     * @param string|null                            $fileEncoding
     *
     * @return \Dotenv\Dotenv
     */
    public static function create(RepositoryInterface $repository, $paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null)
    {
        $builder = $names === null ? StoreBuilder::createWithDefaultName() : StoreBuilder::createWithNoNames();

        foreach ((array) $paths as $path) {
            $builder = $builder->addPath($path);
        }

        foreach ((array) $names as $name) {
            $builder = $builder->addName($name);
        }

        if ($shortCircuit) {
            $builder = $builder->shortCircuit();
        }

        return new self($builder->fileEncoding($fileEncoding)->make(), new Parser(), new Loader(), $repository);
    }

以上方法参数$paths与$names均支持数组传递,因此直接将原配置拆分为多个,然后放到一个固定的目录中去,按照文件后缀读取文件。

原配置文件通过Apollo下载下来的,这次同时在本地配置好了下载的地址,一顿操作之后,启动项目( 当前项目框架为webman),控制台满屏的错误栈抛出,显示错误如下:

Error: Lexer encountered unexpected character [a]. in /Users/jey/workspace/php/multi-service/vendor/vlucas/phpdotenv/src/Parser/Lexer.php:54
Stack trace:
#0 [internal function]: Dotenv\Parser\Lexer::lex('auth.appg...')
#1 /Users/jey/workspace/php/multi-service/vendor/vlucas/phpdotenv/src/Parser/EntryParser.php(160): iterator_to_array(Object(Generator))
#2 /Users/jey/workspace/php/multi-service/vendor/vlucas/phpdotenv/src/Parser/EntryParser.php(53): Dotenv\Parser\EntryParser::parseValue('auth.appg...')
#3 /Users/jey/workspace/php/multi-service/vendor/graham-campbell/result-type/src/Success.php(93): Dotenv\Parser\EntryParser::Dotenv\Parser\{closure}('CORS_ORIGIN_WHI...')
*****省略

具体位置:

 /**
     * The regex for each type of token.
     *
     * @var string
     */
    private const PATTERNS = [
        '[\r\n]{1,1000}', '[^\S\r\n]{1,1000}', '\\\\', '\'', '"', '\\#', '\\$', '([^(\s\\\\\'"\\#\\$)]|\\(|\\)){1,1000}',
    ];

    /**
     * Convert content into a token stream.
     *
     * Multibyte string processing is not needed here, and nether is error
     * handling, for performance reasons.
     *
     * @param string $content
     *
     * @return \Generator<string>
     */
    public static function lex(string $content)
    {
        static $regex;
        if ($regex === null) {
            $regex = '(('.\implode(')|(', self::PATTERNS).'))A';
        }
        $tokens = [];
        $offset = 0;
        while (isset($content[$offset])) {
            if (!\preg_match($regex, $content, $matches, 0, $offset)) {
                throw new \Error(\sprintf('Lexer encountered unexpected character [%s].', $content[$offset]));
            }
            $offset += \strlen($matches[0]);
            yield $matches[0];
        }
    }

出现问题的,就是此处的正则表达式preg_match抛错。后面我顺着IDE的debug模式,顺着执行下来,出错时候的各变量的值为:
$regex = (([\r\n]{1,1000})|([^\S\r\n]{1,1000})|(\)|(‘)|(“)|(#)|(\$)|(([^(\s\'”#\$)]|(|)){1,1000}))A
$content = auth.appg****(省略,很长一串,整个字符长度为882)
$offset = 0
上面这里的正则表达式因为包含单引号双引号的原因,所以如果调试时直接使用上面这串将会出现异常(没有转义),应该使用原本implode的方式去debug。

debug代码:
$op = [
        '[\r\n]{1,1000}', '[^\S\r\n]{1,1000}', '\\\\', '\'', '"', '\\#', '\\$', '([^(\s\\\\\'"\\#\\$)]|\\(|\\)){1,1000}',
    ];
$p = '(('.implode(')|(', $op).'))A';
$offset = 0;
if (!preg_match($p, $str,$matches, 0, $offset)) {
	echo 'err';
}else{
	echo 'ok';
}

本地执行,ok。这就很奇怪了,为什么在项目里启动的时候就会出现这种问题呢?并且同样的配置,在测试环境也是同样没有异常的。最后来来去去(与其他人对比测试),认定为是本地环境的原因导致的(另外一个现象是,我将该配置项的长度缩短,减小到一定的长度时,就正常了)。

在网上找了一会儿,发现可以使用preg_last_error和preg_last_error_msg两个函数能够辅助显示正则匹配最后一次出错的原因,添加后发现错误为:

PREG_JIT_STACKLIMIT_ERROR,官方给出的解释如下:

我查看了当前的配置(左侧为本地配置,右侧为测试环境配置):

对比来看,pcre.backtrack_limit我本地为100000,而测试环境配置为1000000,小了10倍。那就是这个问题吗?随即把该配置调整为与测试环境一致,重试,还是报错。后面尝试继续增大,还是不起作用,后面直接将pcre.jit=1设置为0,这次启动不报错了!

可是根据报错提示,我将该参数的配置已经设置与测试环境一致,为什么还是无法正常启动,目前还是未找到原因……网上找了一些同样的帖子,对方提到的优化点一般都是从正则表达式出发,对表达式进行优化,避免出现回溯,但这种方案目前不适用当前场景。另外从另一个角度来说,配置文件的内容还是不能过长,保不齐到时候正式环境如果该配置项长度增加了,将可能导致同样的问题。

Leave a Reply

您的电子邮箱地址不会被公开。 必填项已用*标注