Merge pull request #3896 from tanhauhau/tanhauhau/loop-protect

add loopGuardTimeout options
pull/3901/head
Rich Harris 5 years ago committed by GitHub
commit 4a3147ff33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -53,6 +53,7 @@ The following options can be passed to the compiler. None are required:
| `tag` | string | null | `tag` | string | null
| `accessors` | boolean | `false` | `accessors` | boolean | `false`
| `css` | boolean | `true` | `css` | boolean | `true`
| `loopGuardTimeout` | number | 0
| `preserveComments` | boolean | `false` | `preserveComments` | boolean | `false`
| `preserveWhitespace` | boolean | `false` | `preserveWhitespace` | boolean | `false`
| `outputFilename` | string | `null` | `outputFilename` | string | `null`
@ -73,6 +74,7 @@ The following options can be passed to the compiler. None are required:
| `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component. | `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component.
| `tag` | `null` | A `string` that tells Svelte what tag name to register the custom element with. It must be a lowercase alphanumeric string with at least one hyphen, e.g. `"my-element"`. | `tag` | `null` | A `string` that tells Svelte what tag name to register the custom element with. It must be a lowercase alphanumeric string with at least one hyphen, e.g. `"my-element"`.
| `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime. It's recommended that you set this to `false` and use the CSS that is statically generated, as it will result in smaller JavaScript bundles and better performance. | `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime. It's recommended that you set this to `false` and use the CSS that is statically generated, as it will result in smaller JavaScript bundles and better performance.
| `loopGuardTimeout` | 0 | A `number` that tells Svelte to break the loop if it blocks the thread for more than `loopGuardTimeout` ms. This is useful to prevent infinite loops. **Only available when `dev: true`**
| `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out. | `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out.
| `preserveWhitespace` | `false` | If `true`, whitespace inside and between elements is kept as you typed it, rather than optimised by Svelte. | `preserveWhitespace` | `false` | If `true`, whitespace inside and between elements is kept as you typed it, rather than optimised by Svelte.
| `outputFilename` | `null` | A `string` used for your JavaScript sourcemap. | `outputFilename` | `null` | A `string` used for your JavaScript sourcemap.

@ -750,8 +750,8 @@ export default class Component {
component.warn_on_undefined_store_value_references(node, parent, scope); component.warn_on_undefined_store_value_references(node, parent, scope);
if (component.compile_options.dev) { if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0) {
const to_insert_for_loop_protect = component.loop_protect(node, prop, index); const to_insert_for_loop_protect = component.loop_protect(node, prop, index, component.compile_options.loopGuardTimeout);
if (to_insert_for_loop_protect) { if (to_insert_for_loop_protect) {
if (!Array.isArray(parent[prop])) { if (!Array.isArray(parent[prop])) {
parent[prop] = { parent[prop] = {
@ -863,7 +863,7 @@ export default class Component {
} }
} }
loop_protect(node, prop, index) { loop_protect(node, prop, index, timeout) {
if (node.type === 'WhileStatement' || if (node.type === 'WhileStatement' ||
node.type === 'ForStatement' || node.type === 'ForStatement' ||
node.type === 'DoWhileStatement') { node.type === 'DoWhileStatement') {
@ -873,7 +873,7 @@ export default class Component {
internal: true, internal: true,
}); });
const before = b`const ${guard} = @loop_guard()`; const before = b`const ${guard} = @loop_guard(${timeout})`;
const inside = b`${guard}();`; const inside = b`${guard}();`;
// wrap expression statement with BlockStatement // wrap expression statement with BlockStatement

@ -24,12 +24,13 @@ const valid_options = [
'customElement', 'customElement',
'tag', 'tag',
'css', 'css',
'loopGuardTimeout',
'preserveComments', 'preserveComments',
'preserveWhitespace' 'preserveWhitespace'
]; ];
function validate_options(options: CompileOptions, warnings: Warning[]) { function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename } = options; const { name, filename, loopGuardTimeout, dev } = options;
Object.keys(options).forEach(key => { Object.keys(options).forEach(key => {
if (valid_options.indexOf(key) === -1) { if (valid_options.indexOf(key) === -1) {
@ -54,6 +55,16 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
toString: () => message, toString: () => message,
}); });
} }
if (loopGuardTimeout && !dev) {
const message = 'options.loopGuardTimeout is for options.dev = true only';
warnings.push({
code: `options-loop-guard-timeout`,
message,
filename,
toString: () => message,
});
}
} }
export default function compile(source: string, options: CompileOptions = {}) { export default function compile(source: string, options: CompileOptions = {}) {

@ -121,6 +121,7 @@ export interface CompileOptions {
customElement?: boolean; customElement?: boolean;
tag?: string; tag?: string;
css?: boolean; css?: boolean;
loopGuardTimeout?: number;
preserveComments?: boolean; preserveComments?: boolean;
preserveWhitespace?: boolean; preserveWhitespace?: boolean;

@ -96,10 +96,10 @@ export class SvelteComponentDev extends SvelteComponent {
} }
} }
export function loop_guard() { export function loop_guard(timeout) {
const start = Date.now(); const start = Date.now();
return () => { return () => {
if (Date.now() - start > 100) { if (Date.now() - start > timeout) {
throw new Error(`Infinite loop detected`); throw new Error(`Infinite loop detected`);
} }
}; };

@ -1,5 +1,6 @@
export default { export default {
options: { options: {
dev: true dev: true,
} loopGuardTimeout: 100,
},
}; };

@ -35,28 +35,28 @@ function create_fragment(ctx) {
} }
function instance($$self) { function instance($$self) {
const guard = loop_guard(); const guard = loop_guard(100);
while (true) { while (true) {
foo(); foo();
guard(); guard();
} }
const guard_1 = loop_guard(); const guard_1 = loop_guard(100);
for (; ; ) { for (; ; ) {
foo(); foo();
guard_1(); guard_1();
} }
const guard_2 = loop_guard(); const guard_2 = loop_guard(100);
while (true) { while (true) {
foo(); foo();
guard_2(); guard_2();
} }
const guard_4 = loop_guard(); const guard_4 = loop_guard(100);
do { do {
foo(); foo();
@ -72,7 +72,7 @@ function instance($$self) {
}; };
$: { $: {
const guard_3 = loop_guard(); const guard_3 = loop_guard(100);
while (true) { while (true) {
foo(); foo();
@ -81,7 +81,7 @@ function instance($$self) {
} }
$: { $: {
const guard_5 = loop_guard(); const guard_5 = loop_guard(100);
do { do {
foo(); foo();

@ -2,5 +2,6 @@ export default {
error: 'Infinite loop detected', error: 'Infinite loop detected',
compileOptions: { compileOptions: {
dev: true, dev: true,
loopGuardTimeout: 100,
} }
}; };

Loading…
Cancel
Save