Skip to content
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

prevent infinite loop #3887

Merged
merged 1 commit into from
Nov 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion src/compiler/compile/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import Slot from './nodes/Slot';
import { Node, ImportDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal } from 'estree';
import add_to_set from './utils/add_to_set';
import check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, x } from 'code-red';
import { print, x, b } from 'code-red';

interface ComponentOptions {
namespace?: string;
Expand Down Expand Up @@ -722,6 +722,8 @@ export default class Component {
toRemove.unshift([parent, prop, index]);
};

const toInsert = new Map();

walk(content, {
enter(node, parent, prop, index) {
if (map.has(node)) {
Expand All @@ -747,12 +749,37 @@ 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 (to_insert_for_loop_protect) {
if (!Array.isArray(parent[prop])) {
parent[prop] = {
type: 'BlockStatement',
body: [to_insert_for_loop_protect.node, node],
};
} else {
// can't insert directly, will screw up the index in the for-loop of estree-walker
if (!toInsert.has(parent)) {
toInsert.set(parent, []);
}
toInsert.get(parent).push(to_insert_for_loop_protect);
}
}
}
},

leave(node) {
if (map.has(node)) {
scope = scope.parent;
}
if (toInsert.has(node)) {
const nodes_to_insert = toInsert.get(node);
for (const { index, prop, node: node_to_insert } of nodes_to_insert.reverse()) {
node[prop].splice(index, 0, node_to_insert);
}
toInsert.delete(node);
}
},
});

Expand Down Expand Up @@ -836,6 +863,35 @@ export default class Component {
}
}

loop_protect(node, prop, index) {
if (node.type === 'WhileStatement' ||
node.type === 'ForStatement' ||
node.type === 'DoWhileStatement') {
const id = this.get_unique_name('LP');
this.add_var({
name: id.name,
internal: true,
});

const before = b`const ${id} = Date.now();`;
const inside = b`
if (Date.now() - ${id} > 100) {
throw new Error('Infinite loop detected');
}
`;
// wrap expression statement with BlockStatement
if (node.body.type !== 'BlockStatement') {
node.body = {
type: 'BlockStatement',
body: [node.body],
};
}
node.body.body.push(inside[0]);
return { index, prop, node: before[0] };
}
return null;
}

invalidate(name, value?) {
const variable = this.var_lookup.get(name);

Expand Down
5 changes: 5 additions & 0 deletions test/js/samples/loop_protect/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
options: {
dev: true
}
};
126 changes: 126 additions & 0 deletions test/js/samples/loop_protect/expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponentDev,
dispatch_dev,
init,
noop,
safe_not_equal
} from "svelte/internal";

const file = undefined;

function create_fragment(ctx) {
const block = {
c: noop,
l: function claim(nodes) {
throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option");
},
m: noop,
p: noop,
i: noop,
o: noop,
d: noop
};

dispatch_dev("SvelteRegisterBlock", {
block,
id: create_fragment.name,
type: "component",
source: "",
ctx
});

return block;
}

function instance($$self) {
const LP = Date.now();

while (true) {
foo();

if (Date.now() - LP > 100) {
throw new Error("Infinite loop detected");
}
}

const LP_1 = Date.now();

for (; ; ) {
foo();

if (Date.now() - LP_1 > 100) {
throw new Error("Infinite loop detected");
}
}

const LP_2 = Date.now();

while (true) {
foo();

if (Date.now() - LP_2 > 100) {
throw new Error("Infinite loop detected");
}
}

const LP_4 = Date.now();

do {
foo();

if (Date.now() - LP_4 > 100) {
throw new Error("Infinite loop detected");
}
} while (true);

$$self.$capture_state = () => {
return {};
};

$$self.$inject_state = $$props => {

};

$: {
const LP_3 = Date.now();

while (true) {
foo();

if (Date.now() - LP_3 > 100) {
throw new Error("Infinite loop detected");
}
}
}

$: {
const LP_5 = Date.now();

do {
foo();

if (Date.now() - LP_5 > 100) {
throw new Error("Infinite loop detected");
}
} while (true);
}

return {};
}

class Component extends SvelteComponentDev {
constructor(options) {
super(options);
init(this, options, instance, create_fragment, safe_not_equal, {});

dispatch_dev("SvelteRegisterComponent", {
component: this,
tagName: "Component",
options,
id: create_fragment.name
});
}
}

export default Component;
12 changes: 12 additions & 0 deletions test/js/samples/loop_protect/input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
while(true) {
foo();
}
for(;;) {
foo();
}
while(true) foo();
$: while(true) foo();
do foo(); while(true);
$: do foo(); while(true);
</script>
6 changes: 6 additions & 0 deletions test/runtime/samples/loop-protect/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default {
error: 'Infinite loop detected',
compileOptions: {
dev: true,
}
};
5 changes: 5 additions & 0 deletions test/runtime/samples/loop-protect/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
while(true) {
// do nothing
}
</script>