fix: stop user code after 100ms of execution (#37841)

Co-authored-by: mrugesh <1884376+raisedadead@users.noreply.github.com>
Co-authored-by: Randell Dawson <5313213+RandellDawson@users.noreply.github.com>
Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>
This commit is contained in:
Oliver Eyton-Williams
2019-12-20 14:58:17 +01:00
committed by mrugesh
parent f5360e9393
commit 01b37f664f
9 changed files with 143 additions and 75 deletions

View File

@ -1236,6 +1236,11 @@
"prop-types": "^15.5.10" "prop-types": "^15.5.10"
} }
}, },
"@freecodecamp/loop-protect": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@freecodecamp/loop-protect/-/loop-protect-2.2.0.tgz",
"integrity": "sha512-WwYmQn6Xrk7KE22xANbye/qyg3U0aupWtMaJpN/k22/cdSBpWyjdrGSStEqg3DxFxdJoR/O/yhe8gqbtTYKI3g=="
},
"@freecodecamp/react-bootstrap": { "@freecodecamp/react-bootstrap": {
"version": "0.32.3", "version": "0.32.3",
"resolved": "https://registry.npmjs.org/@freecodecamp/react-bootstrap/-/react-bootstrap-0.32.3.tgz", "resolved": "https://registry.npmjs.org/@freecodecamp/react-bootstrap/-/react-bootstrap-0.32.3.tgz",
@ -15513,11 +15518,6 @@
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.3.tgz", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.3.tgz",
"integrity": "sha512-9lz5IVdpwsKLMzQi0MQ+oD9EA0mIGcWYP7jXMTZVXP8D42PwuAk+M/HBFYQoxt1G5OR8m7aSIgb1UymfWGBWEw==" "integrity": "sha512-9lz5IVdpwsKLMzQi0MQ+oD9EA0mIGcWYP7jXMTZVXP8D42PwuAk+M/HBFYQoxt1G5OR8m7aSIgb1UymfWGBWEw=="
}, },
"loop-protect": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/loop-protect/-/loop-protect-2.1.6.tgz",
"integrity": "sha512-eGNk917T5jQ9A/ER/zJlEXCGD/NQepYyLnLBgVPSuspHauG2HUiDx5oKDSpyVQOzGb+yUKMA1k41+Old2ZmcRQ=="
},
"loose-envify": { "loose-envify": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",

View File

@ -14,6 +14,7 @@
"@fortawesome/free-regular-svg-icons": "^5.11.2", "@fortawesome/free-regular-svg-icons": "^5.11.2",
"@fortawesome/free-solid-svg-icons": "^5.11.2", "@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.4", "@fortawesome/react-fontawesome": "^0.1.4",
"@freecodecamp/loop-protect": "^2.2.0",
"@freecodecamp/react-bootstrap": "^0.32.3", "@freecodecamp/react-bootstrap": "^0.32.3",
"@reach/router": "^1.2.1", "@reach/router": "^1.2.1",
"algoliasearch": "^3.35.1", "algoliasearch": "^3.35.1",
@ -40,7 +41,6 @@
"gatsby-transformer-remark": "^2.6.30", "gatsby-transformer-remark": "^2.6.30",
"jquery": "^3.4.1", "jquery": "^3.4.1",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"loop-protect": "^2.1.6",
"monaco-editor": "^0.18.1", "monaco-editor": "^0.18.1",
"monaco-editor-webpack-plugin": "^1.7.0", "monaco-editor-webpack-plugin": "^1.7.0",
"nanoid": "^1.2.2", "nanoid": "^1.2.2",

View File

@ -13,7 +13,7 @@ import {
import * as Babel from '@babel/standalone'; import * as Babel from '@babel/standalone';
import presetEnv from '@babel/preset-env'; import presetEnv from '@babel/preset-env';
import presetReact from '@babel/preset-react'; import presetReact from '@babel/preset-react';
import protect from 'loop-protect'; import protect from '@freecodecamp/loop-protect';
import * as vinyl from '../utils/polyvinyl.js'; import * as vinyl from '../utils/polyvinyl.js';
import createWorker from '../utils/worker-executor'; import createWorker from '../utils/worker-executor';
@ -23,7 +23,26 @@ import createWorker from '../utils/worker-executor';
import { filename as sassCompile } from '../../../../config/sass-compile'; import { filename as sassCompile } from '../../../../config/sass-compile';
const protectTimeout = 100; const protectTimeout = 100;
Babel.registerPlugin('loopProtection', protect(protectTimeout)); const testProtectTimeout = 1500;
const loopsPerTimeoutCheck = 2000;
function loopProtectCB(line) {
console.log(
`Potential infinite loop detected on line ${line}. Tests may fail if this is not changed.`
);
}
function testLoopProtectCB(line) {
console.log(
`Potential infinite loop detected on line ${line}. Tests may be failing because of this.`
);
}
Babel.registerPlugin('loopProtection', protect(protectTimeout, loopProtectCB));
Babel.registerPlugin(
'testLoopProtection',
protect(testProtectTimeout, testLoopProtectCB, loopsPerTimeoutCheck)
);
const babelOptionsJSX = { const babelOptionsJSX = {
plugins: ['loopProtection'], plugins: ['loopProtection'],
@ -31,9 +50,15 @@ const babelOptionsJSX = {
}; };
const babelOptionsJS = { const babelOptionsJS = {
plugins: ['testLoopProtection'],
presets: [presetEnv] presets: [presetEnv]
}; };
const babelOptionsJSPreview = {
...babelOptionsJS,
plugins: ['loopProtection']
};
const babelTransformCode = options => code => const babelTransformCode = options => code =>
Babel.transform(code, options).code; Babel.transform(code, options).code;
@ -69,28 +94,31 @@ function tryTransform(wrap = identity) {
}; };
} }
export const babelTransformer = cond([ const babelTransformer = (preview = false) =>
[ cond([
testJS, [
flow( testJS,
partial( flow(
vinyl.transformHeadTailAndContents, partial(
tryTransform(babelTransformCode(babelOptionsJS)) vinyl.transformHeadTailAndContents,
tryTransform(
babelTransformCode(preview ? babelOptionsJSPreview : babelOptionsJS)
)
)
) )
) ],
], [
[ testJSX,
testJSX, flow(
flow( partial(
partial( vinyl.transformHeadTailAndContents,
vinyl.transformHeadTailAndContents, tryTransform(babelTransformCode(babelOptionsJSX))
tryTransform(babelTransformCode(babelOptionsJSX)) ),
), partial(vinyl.setExt, 'js')
partial(vinyl.setExt, 'js') )
) ],
], [stubTrue, identity]
[stubTrue, identity] ]);
]);
const sassWorker = createWorker(sassCompile); const sassWorker = createWorker(sassCompile);
async function transformSASS(element) { async function transformSASS(element) {
@ -141,7 +169,14 @@ export const htmlTransformer = cond([
export const transformers = [ export const transformers = [
replaceNBSP, replaceNBSP,
babelTransformer, babelTransformer(),
composeHTML,
htmlTransformer
];
export const transformersPreview = [
replaceNBSP,
babelTransformer(true),
composeHTML, composeHTML,
htmlTransformer htmlTransformer
]; ];

View File

@ -33,6 +33,9 @@ import {
isJavaScriptChallenge isJavaScriptChallenge
} from '../utils/build'; } from '../utils/build';
// How long before bailing out of a preview.
const previewTimeout = 2500;
export function* executeChallengeSaga() { export function* executeChallengeSaga() {
const isBuildEnabled = yield select(isBuildEnabledSelector); const isBuildEnabled = yield select(isBuildEnabledSelector);
if (!isBuildEnabled) { if (!isBuildEnabled) {
@ -90,9 +93,9 @@ function* takeEveryConsole(channel) {
}); });
} }
function* buildChallengeData(challengeData) { function* buildChallengeData(challengeData, preview) {
try { try {
return yield call(buildChallenge, challengeData); return yield call(buildChallenge, challengeData, preview);
} catch (e) { } catch (e) {
yield put(disableBuildOnError()); yield put(disableBuildOnError());
throw e; throw e;
@ -155,7 +158,7 @@ function* previewChallengeSaga() {
const challengeData = yield select(challengeDataSelector); const challengeData = yield select(challengeDataSelector);
if (canBuildChallenge(challengeData)) { if (canBuildChallenge(challengeData)) {
const buildData = yield buildChallengeData(challengeData); const buildData = yield buildChallengeData(challengeData, true);
// evaluate the user code in the preview frame or in the worker // evaluate the user code in the preview frame or in the worker
if (challengeHasPreview(challengeData)) { if (challengeHasPreview(challengeData)) {
const document = yield getContext('document'); const document = yield getContext('document');
@ -163,10 +166,14 @@ function* previewChallengeSaga() {
} else if (isJavaScriptChallenge(challengeData)) { } else if (isJavaScriptChallenge(challengeData)) {
const runUserCode = getTestRunner(buildData, { proxyLogger }); const runUserCode = getTestRunner(buildData, { proxyLogger });
// without a testString the testRunner just evaluates the user's code // without a testString the testRunner just evaluates the user's code
yield call(runUserCode, null, 5000); yield call(runUserCode, null, previewTimeout);
} }
} }
} catch (err) { } catch (err) {
if (err === 'timeout') {
// eslint-disable-next-line no-ex-assign
err = `The code you have written is taking longer than the ${previewTimeout}ms our challenges allow. You may have created an infinite loop or need to write a more efficient algorithm`;
}
console.log(err); console.log(err);
yield put(updateConsole(escape(err))); yield put(updateConsole(escape(err)));
} }

View File

@ -1,4 +1,4 @@
import { transformers } from '../rechallenge/transformers'; import { transformers, transformersPreview } from '../rechallenge/transformers';
import { cssToHtml, jsToHtml, concatHtml } from '../rechallenge/builders.js'; import { cssToHtml, jsToHtml, concatHtml } from '../rechallenge/builders.js';
import { challengeTypes } from '../../../../utils/challengeTypes'; import { challengeTypes } from '../../../../utils/challengeTypes';
import createWorker from './worker-executor'; import createWorker from './worker-executor';
@ -76,11 +76,11 @@ export function canBuildChallenge(challengeData) {
return buildFunctions.hasOwnProperty(challengeType); return buildFunctions.hasOwnProperty(challengeType);
} }
export async function buildChallenge(challengeData) { export async function buildChallenge(challengeData, preview = false) {
const { challengeType } = challengeData; const { challengeType } = challengeData;
let build = buildFunctions[challengeType]; let build = buildFunctions[challengeType];
if (build) { if (build) {
return build(challengeData); return build(challengeData, preview);
} }
throw new Error(`Cannot build challenge of type ${challengeType}`); throw new Error(`Cannot build challenge of type ${challengeType}`);
} }
@ -137,8 +137,10 @@ export function buildDOMChallenge({ files, required = [], template = '' }) {
})); }));
} }
export function buildJSChallenge({ files }) { export function buildJSChallenge({ files }, preview = false) {
const pipeLine = composeFunctions(...transformers); const pipeLine = preview
? composeFunctions(...transformersPreview)
: composeFunctions(...transformers);
const finalFiles = Object.keys(files) const finalFiles = Object.keys(files)
.map(key => files[key]) .map(key => files[key])
.map(pipeLine); .map(pipeLine);

View File

@ -62,44 +62,67 @@ divisibleTriangleNumber(500);
</div> </div>
</section> </section>
## Solution ## Solution
<section id='solution'> <section id='solution'>
```js ```js
function divisibleTriangleNumber(n) { function divisibleTriangleNumber(n) {
if (n === 1) return 3;
let counter = 1; let counter = 1;
let triangleNumber = counter++; let triangleNumber = counter++;
function getFactors(num) {
let factors = [];
let possibleFactor = 1; while (noOfFactors(triangleNumber) < n) {
let sqrt = Math.sqrt(num); triangleNumber += counter++;
}
return triangleNumber;
}
while (possibleFactor <= sqrt) { function noOfFactors(num) {
if (num % possibleFactor == 0) { const primeFactors = getPrimeFactors(num);
factors.push(possibleFactor); let prod = 1;
var otherPossibleFactor = num / possibleFactor; for(let p in primeFactors) {
if (otherPossibleFactor > possibleFactor) { prod *= (primeFactors[p] + 1)
factors.push(otherPossibleFactor); }
} return prod;
}
function getPrimeFactors(num) {
let n = num;
let primes = {};
let p = 2;
let sqrt = Math.sqrt(num);
function checkAndUpdate(inc) {
if (n % p === 0) {
const curr = primes[p];
if (curr) {
primes[p]++
} else {
primes[p] = 1;
} }
possibleFactor++; n /= p;
} else {
p += inc;
} }
return factors;
} }
while (getFactors(triangleNumber).length < n) { while(p === 2 && p <= n) {
triangleNumber += counter++; checkAndUpdate(1);
} }
console.log(triangleNumber)
return triangleNumber; while (p <= n && p <= sqrt) {
checkAndUpdate(2);
}
if(Object.keys(primes).length === 0) {
primes[num] = 1;
} else if(n !== 1) {
primes[n] = 1;
}
return primes;
} }
``` ```

View File

@ -35,8 +35,8 @@ tests:
testString: assert.strictEqual(longestCollatzSequence(46500), 35655); testString: assert.strictEqual(longestCollatzSequence(46500), 35655);
- text: <code>longestCollatzSequence(54512)</code> should return 52527. - text: <code>longestCollatzSequence(54512)</code> should return 52527.
testString: assert.strictEqual(longestCollatzSequence(54512), 52527); testString: assert.strictEqual(longestCollatzSequence(54512), 52527);
- text: <code>longestCollatzSequence(1000000)</code> should return 837799. - text: <code>longestCollatzSequence(100000)</code> should return 77031.
testString: assert.strictEqual(longestCollatzSequence(1000000), 837799); testString: assert.strictEqual(longestCollatzSequence(100000), 77031);
``` ```

View File

@ -61,21 +61,22 @@ nthPrime(10001);
```js ```js
const nthPrime = (number)=>{ const nthPrime = n => {
let pN = 2; let pN = 2;
let step = 0; let step = 0;
while (step<number) { while (step < n) {
let isPrime = true; let isPrime = true;
for(let i = 2;i<pN;i++){ let rootN = Math.sqrt(pN);
if(!(pN%i)){ for (let i = 2; i <= rootN; i++) {
if (!(pN % i)) {
isPrime = false; isPrime = false;
break; break;
} }
} }
isPrime ? step++ : ''; isPrime ? step++ : '';
pN++; pN++;
} }
return pN-1; return pN - 1;
} }
``` ```

View File

@ -31,8 +31,8 @@ tests:
testString: assert(typeof emirps === 'function'); testString: assert(typeof emirps === 'function');
- text: <code>emirps(20,true)</code> should return <code>[13,17,31,37,71,73,79,97,107,113,149,157,167,179,199,311,337,347,359,389]</code> - text: <code>emirps(20,true)</code> should return <code>[13,17,31,37,71,73,79,97,107,113,149,157,167,179,199,311,337,347,359,389]</code>
testString: assert.deepEqual(emirps(20, true), [13, 17, 31, 37, 71, 73, 79, 97, 107, 113, 149, 157, 167, 179, 199, 311, 337, 347, 359, 389]); testString: assert.deepEqual(emirps(20, true), [13, 17, 31, 37, 71, 73, 79, 97, 107, 113, 149, 157, 167, 179, 199, 311, 337, 347, 359, 389]);
- text: <code>emirps(10000)</code> should return <code>948349</code> - text: <code>emirps(1000)</code> should return <code>70529</code>
testString: assert.deepEqual(emirps(10000), 948349); testString: assert.deepEqual(emirps(1000), 70529);
- text: <code>emirps([7700,8000],true)</code> should return <code>[7717,7757,7817,7841,7867,7879,7901,7927,7949,7951,7963]</code> - text: <code>emirps([7700,8000],true)</code> should return <code>[7717,7757,7817,7841,7867,7879,7901,7927,7949,7951,7963]</code>
testString: assert.deepEqual(emirps([7700, 8000], true), [7717, 7757, 7817, 7841, 7867, 7879, 7901, 7927, 7949, 7951, 7963]); testString: assert.deepEqual(emirps([7700, 8000], true), [7717, 7757, 7817, 7841, 7867, 7879, 7901, 7927, 7949, 7951, 7963]);
- text: <code>emirps([7700,8000],true)</code> should return <code>11</code> - text: <code>emirps([7700,8000],true)</code> should return <code>11</code>