fix infinite loop in soy mode and make tokenizer more robust

This commit is contained in:
nightwing 2014-11-05 16:42:39 +04:00
commit 70337ea5ba
3 changed files with 55 additions and 11 deletions

View file

@ -188,7 +188,7 @@ var SoyTemplateHighlightRules = function() {
[ 'entity.other.attribute-name.soy', [ 'entity.other.attribute-name.soy',
'text', 'text',
'keyword.operator.soy' ], 'keyword.operator.soy' ],
regex: '\\b([\\w]*)(\\s*)((?::)?)' }, regex: '\\b([\\w]+)(\\s*)((?::)?)' },
{ defaultToken: 'meta.tag.param.soy' } ] } ], { defaultToken: 'meta.tag.param.soy' } ] } ],
'#primitive': '#primitive':
[ { token: 'constant.language.soy', [ { token: 'constant.language.soy',

View file

@ -32,7 +32,7 @@ define(function(require, exports, module) {
"use strict"; "use strict";
// tokenizing lines longer than this makes editor very slow // tokenizing lines longer than this makes editor very slow
var MAX_TOKEN_COUNT = 1000; var MAX_TOKEN_COUNT = 2000;
/** /**
* This class takes a set of highlighting rules, and creates a tokenizer out of them. For more information, see [the wiki on extending highlighters](https://github.com/ajaxorg/ace/wiki/Creating-or-Extending-an-Edit-Mode#wiki-extendingTheHighlighter). * This class takes a set of highlighting rules, and creates a tokenizer out of them. For more information, see [the wiki on extending highlighters](https://github.com/ajaxorg/ace/wiki/Creating-or-Extending-an-Edit-Mode#wiki-extendingTheHighlighter).
* @class Tokenizer * @class Tokenizer
@ -77,9 +77,11 @@ var Tokenizer = function(rules) {
if (rule.token.length == 1 || matchcount == 1) { if (rule.token.length == 1 || matchcount == 1) {
rule.token = rule.token[0]; rule.token = rule.token[0];
} else if (matchcount - 1 != rule.token.length) { } else if (matchcount - 1 != rule.token.length) {
throw new Error("number of classes and regexp groups in '" + this.reportError("number of classes and regexp groups doesn't match", {
rule.token + "'\n'" + rule.regex + "' doesn't match\n" rule: rule,
+ (matchcount - 1) + "!=" + rule.token.length); groupCount: matchcount - 1
});
rule.token = rule.token[0];
} else { } else {
rule.tokenArray = rule.token; rule.tokenArray = rule.token;
rule.token = null; rule.token = null;
@ -240,6 +242,7 @@ var Tokenizer = function(rules) {
var match, tokens = []; var match, tokens = [];
var lastIndex = 0; var lastIndex = 0;
var matchAttempts = 0;
var token = {type: null, value: ""}; var token = {type: null, value: ""};
@ -280,7 +283,7 @@ var Tokenizer = function(rules) {
state = this.states[currentState]; state = this.states[currentState];
if (!state) { if (!state) {
window.console && console.error && console.error(currentState, "doesn't exist"); this.reportError("state doesn't exist", currentState);
currentState = "start"; currentState = "start";
state = this.states[currentState]; state = this.states[currentState];
} }
@ -293,7 +296,7 @@ var Tokenizer = function(rules) {
} }
if (value) { if (value) {
if (typeof type == "string") { if (typeof type === "string") {
if ((!rule || rule.merge !== false) && token.type === type) { if ((!rule || rule.merge !== false) && token.type === type) {
token.value += value; token.value += value;
} else { } else {
@ -315,7 +318,13 @@ var Tokenizer = function(rules) {
lastIndex = index; lastIndex = index;
if (tokens.length > MAX_TOKEN_COUNT) { if (matchAttempts++ > MAX_TOKEN_COUNT) {
if (matchAttempts > 2 * line.length) {
this.reportError("infinite loop with in ace tokenizer", {
startState: startState,
line: line
});
}
// chrome doens't show contents of text nodes with very long text // chrome doens't show contents of text nodes with very long text
while (lastIndex < line.length) { while (lastIndex < line.length) {
if (token.type) if (token.type)
@ -344,6 +353,13 @@ var Tokenizer = function(rules) {
}; };
}; };
this.reportError = function(msg, data) {
var e = new Error(msg);
e.data = data;
if (typeof console == "object" && console.error)
console.error(e);
setTimeout(function() { throw e; });
};
}).call(Tokenizer.prototype); }).call(Tokenizer.prototype);
exports.Tokenizer = Tokenizer; exports.Tokenizer = Tokenizer;

View file

@ -29,7 +29,7 @@
* ***** END LICENSE BLOCK ***** */ * ***** END LICENSE BLOCK ***** */
if (typeof process !== "undefined") { if (typeof process !== "undefined") {
require("amd-loader"); require("amd-loader");
} }
define(function(require, exports, module) { define(function(require, exports, module) {
@ -59,7 +59,35 @@ module.exports = {
var t = new Tokenizer({}); var t = new Tokenizer({});
var re = t.removeCapturingGroups("(ax(by))[()]"); var re = t.removeCapturingGroups("(ax(by))[()]");
assert.equal(re, "(?:ax(?:by))[()]"); assert.equal(re, "(?:ax(?:by))[()]");
} },
"test: broken highlight rules": function() {
var t = new Tokenizer({
start: [{
token: 's',
regex: '&&&|^^^'
}, {
defaultToken: "def"
}],
state1: [{
token: 'x',
regex: /\b([\w]*)(\s*)((?::)?)/
}]
});
var errorReports = 0;
t.reportError = function() { errorReports++; };
var tokens = t.getLineTokens("x|", "start");
assert.deepEqual(tokens, {
tokens: [{value: 'x|', type: 'overflow'}],
state: 'start'
});
var tokens = t.getLineTokens("x|", "state1");
assert.deepEqual(tokens, {
tokens: [{value: 'x', type: 'x'}, {value: '|', type: 'overflow'}],
state: 'start'
});
assert.equal(errorReports, 2);
},
}; };
}); });