-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
エラーハンドリングを実装 #46
base: develop
Are you sure you want to change the base?
エラーハンドリングを実装 #46
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -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<EpsilonNFA, 'initialState' | 'acceptingState'> { | ||||||
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') }; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
}); | ||||||
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, | ||||||
}; | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const enfa = buildEpsilonNFA(pat); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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' | ||
>; | ||
Comment on lines
+136
to
+139
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. たぶん |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
このコミットのものではないですが、以下のほうが良い気がしたのでついでに修正してもらいたいです……。