From b382b9a9c62691e1566dcd5177655fee4282c46a Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 11 Nov 2019 22:50:48 +0800 Subject: [PATCH] add loopGuardTimeout options --- site/content/docs/04-compile-time.md | 2 ++ src/compiler/compile/Component.ts | 8 ++++---- src/compiler/compile/index.ts | 13 ++++++++++++- src/compiler/interfaces.ts | 1 + src/runtime/internal/dev.ts | 4 ++-- test/js/samples/loop-protect/_config.js | 7 ++++--- test/js/samples/loop-protect/expected.js | 14 +++++++------- test/runtime/samples/loop-protect/_config.js | 1 + 8 files changed, 33 insertions(+), 17 deletions(-) diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md index c6675e8a0b..407b1dfc13 100644 --- a/site/content/docs/04-compile-time.md +++ b/site/content/docs/04-compile-time.md @@ -53,6 +53,7 @@ The following options can be passed to the compiler. None are required: | `tag` | string | null | `accessors` | boolean | `false` | `css` | boolean | `true` +| `loopGuardTimeout` | number | 0 | `preserveComments` | boolean | `false` | `preserveWhitespace` | boolean | `false` | `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. | `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. +| `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. | `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. diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index b554a1be21..2d696ad306 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -750,8 +750,8 @@ export default class Component { component.warn_on_undefined_store_value_references(node, parent, scope); - if (component.compile_options.dev) { - const to_insert_for_loop_protect = component.loop_protect(node, prop, index); + if (component.compile_options.dev && component.compile_options.loopGuardTimeout > 0) { + const to_insert_for_loop_protect = component.loop_protect(node, prop, index, component.compile_options.loopGuardTimeout); if (to_insert_for_loop_protect) { if (!Array.isArray(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' || node.type === 'ForStatement' || node.type === 'DoWhileStatement') { @@ -873,7 +873,7 @@ export default class Component { internal: true, }); - const before = b`const ${guard} = @loop_guard()`; + const before = b`const ${guard} = @loop_guard(${timeout})`; const inside = b`${guard}();`; // wrap expression statement with BlockStatement diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 98004ba5da..ac52f59471 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -24,12 +24,13 @@ const valid_options = [ 'customElement', 'tag', 'css', + 'loopGuardTimeout', 'preserveComments', 'preserveWhitespace' ]; function validate_options(options: CompileOptions, warnings: Warning[]) { - const { name, filename } = options; + const { name, filename, loopGuardTimeout, dev } = options; Object.keys(options).forEach(key => { if (valid_options.indexOf(key) === -1) { @@ -54,6 +55,16 @@ function validate_options(options: CompileOptions, warnings: Warning[]) { 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 = {}) { diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index f877b93b56..e7362b9313 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -121,6 +121,7 @@ export interface CompileOptions { customElement?: boolean; tag?: string; css?: boolean; + loopGuardTimeout?: number; preserveComments?: boolean; preserveWhitespace?: boolean; diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index c10b339632..404d0b643a 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -96,10 +96,10 @@ export class SvelteComponentDev extends SvelteComponent { } } -export function loop_guard() { +export function loop_guard(timeout) { const start = Date.now(); return () => { - if (Date.now() - start > 100) { + if (Date.now() - start > timeout) { throw new Error(`Infinite loop detected`); } }; diff --git a/test/js/samples/loop-protect/_config.js b/test/js/samples/loop-protect/_config.js index b1f2518e8a..fcbf8be09b 100644 --- a/test/js/samples/loop-protect/_config.js +++ b/test/js/samples/loop-protect/_config.js @@ -1,5 +1,6 @@ export default { options: { - dev: true - } -}; \ No newline at end of file + dev: true, + loopGuardTimeout: 100, + }, +}; diff --git a/test/js/samples/loop-protect/expected.js b/test/js/samples/loop-protect/expected.js index bbb58c8194..59d9a7e43b 100644 --- a/test/js/samples/loop-protect/expected.js +++ b/test/js/samples/loop-protect/expected.js @@ -35,28 +35,28 @@ function create_fragment(ctx) { } function instance($$self) { - const guard = loop_guard(); + const guard = loop_guard(100); while (true) { foo(); guard(); } - const guard_1 = loop_guard(); + const guard_1 = loop_guard(100); for (; ; ) { foo(); guard_1(); } - const guard_2 = loop_guard(); + const guard_2 = loop_guard(100); while (true) { foo(); guard_2(); } - const guard_4 = loop_guard(); + const guard_4 = loop_guard(100); do { foo(); @@ -68,11 +68,11 @@ function instance($$self) { }; $$self.$inject_state = $$props => { - + }; $: { - const guard_3 = loop_guard(); + const guard_3 = loop_guard(100); while (true) { foo(); @@ -81,7 +81,7 @@ function instance($$self) { } $: { - const guard_5 = loop_guard(); + const guard_5 = loop_guard(100); do { foo(); diff --git a/test/runtime/samples/loop-protect/_config.js b/test/runtime/samples/loop-protect/_config.js index 2a1f5cad4f..75f75c003d 100644 --- a/test/runtime/samples/loop-protect/_config.js +++ b/test/runtime/samples/loop-protect/_config.js @@ -2,5 +2,6 @@ export default { error: 'Infinite loop detected', compileOptions: { dev: true, + loopGuardTimeout: 100, } };