#208 - Added a time limits for regex matchers

This commit is contained in:
Simon Cambier
2023-03-12 11:30:56 +01:00
parent 2b2709d9ab
commit 23640f4b0b
3 changed files with 37 additions and 14 deletions

View File

@@ -14,6 +14,7 @@ import {
splitCamelCase, splitCamelCase,
stringsToRegex, stringsToRegex,
stripMarkdownCharacters, stripMarkdownCharacters,
warnDebug,
} from '../tools/utils' } from '../tools/utils'
import { Notice } from 'obsidian' import { Notice } from 'obsidian'
import type { Query } from './query' import type { Query } from './query'
@@ -144,11 +145,11 @@ export class Omnisearch {
logDebug('Indexing into search engine', docs) logDebug('Indexing into search engine', docs)
// Update the list of indexed docs // Update the list of indexed docs
docs.forEach(doc => this.indexedDocuments.set(doc.path, doc.mtime)) docs.forEach(doc => this.indexedDocuments.set(doc.path, doc.mtime))
// Discard files that may have been already added (though it shouldn't happen) // Discard files that may have been already added (though it shouldn't happen)
const alreadyAdded = docs.filter(doc => this.minisearch.has(doc.path)) const alreadyAdded = docs.filter(doc => this.minisearch.has(doc.path))
this.removeFromPaths(alreadyAdded.map(o => o.path)) this.removeFromPaths(alreadyAdded.map(o => o.path))
// Add docs to minisearch // Add docs to minisearch
await this.minisearch.addAllAsync(docs) await this.minisearch.addAllAsync(docs)
} }
@@ -304,11 +305,16 @@ export class Omnisearch {
} }
public getMatches(text: string, reg: RegExp, query: Query): SearchMatch[] { public getMatches(text: string, reg: RegExp, query: Query): SearchMatch[] {
const startTime = new Date().getTime()
let match: RegExpExecArray | null = null let match: RegExpExecArray | null = null
const matches: SearchMatch[] = [] const matches: SearchMatch[] = []
let count = 0 let count = 0
while ((match = reg.exec(text)) !== null) { while ((match = reg.exec(text)) !== null) {
if (++count >= 100) break // Avoid infinite loops, stop looking after 100 matches // Avoid infinite loops, stop looking after 100 matches or if we're taking too much time
if (++count >= 100 || new Date().getTime() - startTime > 50) {
warnDebug('Stopped getMatches at', count, 'results')
break
}
const m = match[0] const m = match[0]
if (m) matches.push({ match: m, offset: match.index }) if (m) matches.push({ match: m, offset: match.index })
} }

View File

@@ -325,8 +325,18 @@ export function splitCamelCase(text: string): string[] {
return text.replace(/([a-z](?=[A-Z]))/g, '$1 ').split(' ') return text.replace(/([a-z](?=[A-Z]))/g, '$1 ').split(' ')
} }
export function logDebug(...attr: any[]): void { export function logDebug(...args: any[]): void {
printDebug(console.log, ...args)
}
export function warnDebug(...args: any[]): void {
printDebug(console.warn, ...args)
}
function printDebug(fn: (...args: any[]) => any, ...args: any[]): void {
if (settings.verboseLogging) { if (settings.verboseLogging) {
console.log(...['Omnisearch -', ...attr]) const t = new Date()
const ts = `${t.getMinutes()}:${t.getSeconds()}:${t.getMilliseconds()}`
fn(...['Omnisearch -', ts + ' -', ...args])
} }
} }

View File

@@ -6,6 +6,8 @@
* MIT Licensed * MIT Licensed
*/ */
import { warnDebug } from "../tools/utils";
interface SearchParserOptions { interface SearchParserOptions {
offsets?: boolean offsets?: boolean
tokenize: true tokenize: true
@@ -30,7 +32,7 @@ type SearchParserTextOffset = {
type SearchParserOffset = ( type SearchParserOffset = (
| SearchParserKeyWordOffset | SearchParserKeyWordOffset
| SearchParserTextOffset | SearchParserTextOffset
) & { ) & {
offsetStart: number offsetStart: number
offsetEnd: number offsetEnd: number
} }
@@ -43,7 +45,7 @@ interface SearchParserResult extends ISearchParserDictionary {
export function parseQuery( export function parseQuery(
string: string, string: string,
options: SearchParserOptions options: SearchParserOptions,
): SearchParserResult { ): SearchParserResult {
// Set a default options object when none is provided // Set a default options object when none is provided
if (!options) { if (!options) {
@@ -74,9 +76,14 @@ export function parseQuery(
const regex = const regex =
/(\S+:'(?:[^'\\]|\\.)*')|(\S+:"(?:[^"\\]|\\.)*")|(-?"(?:[^"\\]|\\.)*")|(-?'(?:[^'\\]|\\.)*')|\S+|\S+:\S+/g /(\S+:'(?:[^'\\]|\\.)*')|(\S+:"(?:[^"\\]|\\.)*")|(-?"(?:[^"\\]|\\.)*")|(-?'(?:[^'\\]|\\.)*')|\S+|\S+:\S+/g
let match let match
let count = 0 // TODO: FIXME: this is a hack to avoid infinite loops let count = 0
const startTime = new Date().getTime()
while ((match = regex.exec(string)) !== null) { while ((match = regex.exec(string)) !== null) {
if (++count >= 100) break if (++count >= 100 || new Date().getTime() - startTime > 50) {
warnDebug('Stopped SearchParserResult at', count, 'results')
break
}
let term = match[0] let term = match[0]
const sepIndex = term.indexOf(':') const sepIndex = term.indexOf(':')
@@ -291,11 +298,11 @@ export function parseQuery(
query[key].from = rangeValues[0] query[key].from = rangeValues[0]
query[key].to = rangeValues[1] query[key].to = rangeValues[1]
} }
// When pairs of ranges are specified // When pairs of ranges are specified
// keyword:XXXX-YYYY,AAAA-BBBB // keyword:XXXX-YYYY,AAAA-BBBB
// else if (!rangeValues.length % 2) { // else if (!rangeValues.length % 2) {
// } // }
// When only getting a single value, // When only getting a single value,
// or an odd number of values // or an odd number of values
else { else {
query[key].from = value query[key].from = value