From edf9b7eeb4092135d9e496d95c4308135b7ef833 Mon Sep 17 00:00:00 2001 From: yuziroppe Date: Fri, 18 Dec 2020 14:38:11 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=8F=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=83=AA=E3=83=B3=E3=82=B0=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enfa.ts | 78 ++++++++++++++++++++++++++++++++++++++++------------ src/index.ts | 51 ++++++++++++++++------------------ src/test.ts | 6 ++++ src/types.ts | 7 +++++ 4 files changed, 97 insertions(+), 45 deletions(-) diff --git a/src/enfa.ts b/src/enfa.ts index 4456d99..893814b 100644 --- a/src/enfa.ts +++ b/src/enfa.ts @@ -1,12 +1,19 @@ import { Pattern, Node } from 'rerejs'; import { State } from './state'; -import { EpsilonNFA, NullableTransition, Char, Atom } from './types'; +import { + EpsilonNFA, + NullableTransition, + Char, + Atom, + BuildChildResult, + ErrorType, +} from './types'; import { extendAlphabet, getChars } from './char'; /** * Thompson's construction を用いて rerejs.Pattern から ε-NFA を構築する。 */ -export function buildEpsilonNFA(pattern: Pattern): EpsilonNFA { +export function buildEpsilonNFA(pattern: Pattern): EpsilonNFA | ErrorType { return new EpsilonNFABuilder(pattern).build(); } @@ -23,9 +30,14 @@ class EpsilonNFABuilder { constructor(private pattern: Pattern) {} - build(): EpsilonNFA { - let { initialState, acceptingState } = this.buildChild(this.pattern.child); + build(): EpsilonNFA | ErrorType { + const resultOfBuildChild = this.buildChild(this.pattern.child); + if (resultOfBuildChild.type === 'Error') { + return resultOfBuildChild; + } + + let { initialState, acceptingState } = resultOfBuildChild; // submatch用の処理 if ( this.pattern.child.type === 'Capture' || @@ -77,17 +89,26 @@ class EpsilonNFABuilder { * - 初期状態 q への遷移は持たない。 * - 受理状態 f からの遷移は持たない。 */ - private buildChild( - node: Node, - ): Pick { + private buildChild(node: Node): BuildChildResult | ErrorType { switch (node.type) { case 'Disjunction': { const q0 = this.createState(); - const childNFAs = node.children + const maybeChildNFAs = node.children .filter( (child) => child.type !== 'LineBegin' && child.type !== 'LineEnd', ) .map((child) => this.buildChild(child)); + + const childNFAs: BuildChildResult[] = []; + + for (const childNFA of maybeChildNFAs) { + if (childNFA.type === 'BuildChildResult') { + childNFAs.push(childNFA); + } else if (childNFA.type === 'Error') { + return { type: 'Error', error: childNFA.error }; + } + } + const f0 = this.createState(); for (const childNFA of childNFAs) { const q1 = childNFA.initialState; @@ -97,31 +118,47 @@ class EpsilonNFABuilder { } return { + type: 'BuildChildResult', initialState: q0, acceptingState: f0, }; } case 'Sequence': { // ^または$が有効的な場所にない場合、エラーを返す - node.children.forEach((child, index) => { - if (index > 0 && child.type === 'LineBegin') { - throw new Error('Illigal use LineBegin'); + for (let i = 0; i < node.children.length; i++) { + if (i > 0 && node.children[i].type === 'LineBegin') { + return { type: 'Error', error: new Error('Illigal use LineBegin') }; } - if (index < node.children.length - 1 && child.type === 'LineEnd') { - throw new Error('Illigal use LineEnd'); + if ( + i < node.children.length - 1 && + node.children[i].type === 'LineEnd' + ) { + return { type: 'Error', error: new Error('Illigal use LineEnd') }; } - }); - const childNFAs = node.children + } + + const maybeChildNFAs = node.children .filter( (child) => child.type !== 'LineBegin' && child.type !== 'LineEnd', ) .map((child) => this.buildChild(child)); + const childNFAs: BuildChildResult[] = []; + + for (const childNFA of maybeChildNFAs) { + if (childNFA.type === 'BuildChildResult') { + childNFAs.push(childNFA); + } else if (childNFA.type === 'Error') { + return { type: 'Error', error: childNFA.error }; + } + } + if (childNFAs.length === 0) { const q0 = this.createState(); const f0 = this.createState(); this.addTransition(q0, null, f0); return { + type: 'BuildChildResult', initialState: q0, acceptingState: f0, }; @@ -135,6 +172,7 @@ class EpsilonNFABuilder { const f0 = childNFAs[childNFAs.length - 1].acceptingState; return { + type: 'BuildChildResult', initialState: q0, acceptingState: f0, }; @@ -152,6 +190,9 @@ class EpsilonNFABuilder { const optional = node.type === 'Optional'; const q0 = this.createState(); const childNFA = this.buildChild(node.child); + if (childNFA.type === 'Error') { + return { type: 'Error', error: childNFA.error }; + } const f0 = this.createState(); const q1 = childNFA.initialState; const f1 = childNFA.acceptingState; @@ -176,6 +217,7 @@ class EpsilonNFABuilder { } return { + type: 'BuildChildResult', initialState: q0, acceptingState: f0, }; @@ -184,7 +226,7 @@ class EpsilonNFABuilder { case 'WordBoundary': case 'LookAhead': case 'LookBehind': { - throw new Error('unimplemented'); + return { type: 'Error', error: new Error('Unimplemented') }; } case 'Char': case 'EscapeClass': @@ -195,18 +237,20 @@ class EpsilonNFABuilder { this.extendAlphabet(node); this.addTransition(q0, node, f0); return { + type: 'BuildChildResult', initialState: q0, acceptingState: f0, }; } case 'BackRef': case 'NamedBackRef': { - throw new Error('unimplemented'); + return { type: 'Error', error: new Error('Unimplemented') }; } case 'LineBegin': case 'LineEnd': { const q0 = this.throughState(); return { + type: 'BuildChildResult', initialState: q0, acceptingState: q0, }; diff --git a/src/index.ts b/src/index.ts index 7c81e48..43be7ac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,33 +11,28 @@ import { showMessageIDA } from './ida'; import { Message } from './types'; export function detectReDoS(src: string, flags?: string): Message { - try { - const pat = new Parser(src, flags).parse(); - const enfa = buildEpsilonNFA(pat); - const nfa = eliminateEpsilonTransitions(enfa); - const rnfa = reverseNFA(nfa); - const dfa = determinize(rnfa); - const pnfa = prune(nfa, dfa); - const sccs = buildStronglyConnectedComponents(pnfa); - const dps = buildDirectProductGraphs(sccs); - const messageEDA = showMessageEDA(pnfa, dfa, dps); - if (messageEDA.status === 'Vulnerable') { - return messageEDA; - } - const tdps = buildTripleDirectProductGraphs(sccs, pnfa); - const messageIDA = showMessageIDA(pnfa, dfa, tdps); - if (messageIDA.status === 'Vulnerable') { - return messageIDA; - } - return { - status: 'Safe', - message: "Don't have EDA nor IDA", - } as Message; - } catch (e) { - if (e instanceof Error) { - return { status: 'Error', message: e.message }; - } else { - return { status: 'Error', message: 'Undefined Error.' }; - } + const pat = new Parser(src, flags).parse(); + const enfa = buildEpsilonNFA(pat); + if (enfa.type === 'Error') { + return { status: 'Error', message: enfa.error.message }; } + const nfa = eliminateEpsilonTransitions(enfa); + const rnfa = reverseNFA(nfa); + const dfa = determinize(rnfa); + const pnfa = prune(nfa, dfa); + const sccs = buildStronglyConnectedComponents(pnfa); + const dps = buildDirectProductGraphs(sccs); + const messageEDA = showMessageEDA(pnfa, dfa, dps); + if (messageEDA.status === 'Vulnerable') { + return messageEDA; + } + const tdps = buildTripleDirectProductGraphs(sccs, pnfa); + const messageIDA = showMessageIDA(pnfa, dfa, tdps); + if (messageIDA.status === 'Vulnerable') { + return messageIDA; + } + return { + status: 'Safe', + message: "Don't have EDA nor IDA", + } as Message; } diff --git a/src/test.ts b/src/test.ts index 2d4a264..e731ddc 100644 --- a/src/test.ts +++ b/src/test.ts @@ -45,6 +45,7 @@ function main(): void { [String.raw`(.*|(a|a)*)`], // 枝切り1 [String.raw`(a|a)*?.*`], // 枝切り2 [String.raw`^a|b$|aa`], + [String.raw`^ab^c`], // エラー回復用 [String.raw`^$`], // 空文字に一致 ]; @@ -52,6 +53,11 @@ function main(): void { console.log(`//`, src, flags); const pat = new Parser(src, flags).parse(); const enfa = buildEpsilonNFA(pat); + if (enfa.type === 'Error') { + // detectReDosを用いる場合、Objectが返されるがここではmessageのみ + console.error('Error Message:', enfa.error.message); + continue; + } console.log(toDOT(enfa)); console.log(`//`, src, `eliminated`); const nfa = eliminateEpsilonTransitions(enfa); diff --git a/src/types.ts b/src/types.ts index 9b5c429..c62e108 100644 --- a/src/types.ts +++ b/src/types.ts @@ -130,3 +130,10 @@ export type Message = status: 'Error'; message: string; }; + +export type ErrorType = { type: 'Error'; error: Error }; + +export type BuildChildResult = { type: 'BuildChildResult' } & Pick< + EpsilonNFA, + 'initialState' | 'acceptingState' +>;