From 01b37f664f7be09163860d785b1a599f48179328 Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Fri, 20 Dec 2019 14:58:17 +0100 Subject: [PATCH] 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> --- client/package-lock.json | 10 +-- client/package.json | 2 +- .../Challenges/rechallenge/transformers.js | 83 +++++++++++++------ .../redux/execute-challenge-saga.js | 15 +++- .../src/templates/Challenges/utils/build.js | 12 +-- ...hly-divisible-triangular-number.english.md | 65 ++++++++++----- ...lem-14-longest-collatz-sequence.english.md | 4 +- .../problem-7-10001st-prime.english.md | 23 ++--- .../rosetta-code/emirp-primes.english.md | 4 +- 9 files changed, 143 insertions(+), 75 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index c0f90f22c0..b3f4c9144e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1236,6 +1236,11 @@ "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": { "version": "0.32.3", "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", "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": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", diff --git a/client/package.json b/client/package.json index e08641d2e9..f6b01ce3b1 100644 --- a/client/package.json +++ b/client/package.json @@ -14,6 +14,7 @@ "@fortawesome/free-regular-svg-icons": "^5.11.2", "@fortawesome/free-solid-svg-icons": "^5.11.2", "@fortawesome/react-fontawesome": "^0.1.4", + "@freecodecamp/loop-protect": "^2.2.0", "@freecodecamp/react-bootstrap": "^0.32.3", "@reach/router": "^1.2.1", "algoliasearch": "^3.35.1", @@ -40,7 +41,6 @@ "gatsby-transformer-remark": "^2.6.30", "jquery": "^3.4.1", "lodash": "^4.17.15", - "loop-protect": "^2.1.6", "monaco-editor": "^0.18.1", "monaco-editor-webpack-plugin": "^1.7.0", "nanoid": "^1.2.2", diff --git a/client/src/templates/Challenges/rechallenge/transformers.js b/client/src/templates/Challenges/rechallenge/transformers.js index bb80a89ae0..a47a1e4f0d 100644 --- a/client/src/templates/Challenges/rechallenge/transformers.js +++ b/client/src/templates/Challenges/rechallenge/transformers.js @@ -13,7 +13,7 @@ import { import * as Babel from '@babel/standalone'; import presetEnv from '@babel/preset-env'; 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 createWorker from '../utils/worker-executor'; @@ -23,7 +23,26 @@ import createWorker from '../utils/worker-executor'; import { filename as sassCompile } from '../../../../config/sass-compile'; 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 = { plugins: ['loopProtection'], @@ -31,9 +50,15 @@ const babelOptionsJSX = { }; const babelOptionsJS = { + plugins: ['testLoopProtection'], presets: [presetEnv] }; +const babelOptionsJSPreview = { + ...babelOptionsJS, + plugins: ['loopProtection'] +}; + const babelTransformCode = options => code => Babel.transform(code, options).code; @@ -69,28 +94,31 @@ function tryTransform(wrap = identity) { }; } -export const babelTransformer = cond([ - [ - testJS, - flow( - partial( - vinyl.transformHeadTailAndContents, - tryTransform(babelTransformCode(babelOptionsJS)) +const babelTransformer = (preview = false) => + cond([ + [ + testJS, + flow( + partial( + vinyl.transformHeadTailAndContents, + tryTransform( + babelTransformCode(preview ? babelOptionsJSPreview : babelOptionsJS) + ) + ) ) - ) - ], - [ - testJSX, - flow( - partial( - vinyl.transformHeadTailAndContents, - tryTransform(babelTransformCode(babelOptionsJSX)) - ), - partial(vinyl.setExt, 'js') - ) - ], - [stubTrue, identity] -]); + ], + [ + testJSX, + flow( + partial( + vinyl.transformHeadTailAndContents, + tryTransform(babelTransformCode(babelOptionsJSX)) + ), + partial(vinyl.setExt, 'js') + ) + ], + [stubTrue, identity] + ]); const sassWorker = createWorker(sassCompile); async function transformSASS(element) { @@ -141,7 +169,14 @@ export const htmlTransformer = cond([ export const transformers = [ replaceNBSP, - babelTransformer, + babelTransformer(), + composeHTML, + htmlTransformer +]; + +export const transformersPreview = [ + replaceNBSP, + babelTransformer(true), composeHTML, htmlTransformer ]; diff --git a/client/src/templates/Challenges/redux/execute-challenge-saga.js b/client/src/templates/Challenges/redux/execute-challenge-saga.js index 477cf0dbf5..54970ee726 100644 --- a/client/src/templates/Challenges/redux/execute-challenge-saga.js +++ b/client/src/templates/Challenges/redux/execute-challenge-saga.js @@ -33,6 +33,9 @@ import { isJavaScriptChallenge } from '../utils/build'; +// How long before bailing out of a preview. +const previewTimeout = 2500; + export function* executeChallengeSaga() { const isBuildEnabled = yield select(isBuildEnabledSelector); if (!isBuildEnabled) { @@ -90,9 +93,9 @@ function* takeEveryConsole(channel) { }); } -function* buildChallengeData(challengeData) { +function* buildChallengeData(challengeData, preview) { try { - return yield call(buildChallenge, challengeData); + return yield call(buildChallenge, challengeData, preview); } catch (e) { yield put(disableBuildOnError()); throw e; @@ -155,7 +158,7 @@ function* previewChallengeSaga() { const challengeData = yield select(challengeDataSelector); 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 if (challengeHasPreview(challengeData)) { const document = yield getContext('document'); @@ -163,10 +166,14 @@ function* previewChallengeSaga() { } else if (isJavaScriptChallenge(challengeData)) { const runUserCode = getTestRunner(buildData, { proxyLogger }); // without a testString the testRunner just evaluates the user's code - yield call(runUserCode, null, 5000); + yield call(runUserCode, null, previewTimeout); } } } 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); yield put(updateConsole(escape(err))); } diff --git a/client/src/templates/Challenges/utils/build.js b/client/src/templates/Challenges/utils/build.js index e38188c7e5..0121cd957f 100644 --- a/client/src/templates/Challenges/utils/build.js +++ b/client/src/templates/Challenges/utils/build.js @@ -1,4 +1,4 @@ -import { transformers } from '../rechallenge/transformers'; +import { transformers, transformersPreview } from '../rechallenge/transformers'; import { cssToHtml, jsToHtml, concatHtml } from '../rechallenge/builders.js'; import { challengeTypes } from '../../../../utils/challengeTypes'; import createWorker from './worker-executor'; @@ -76,11 +76,11 @@ export function canBuildChallenge(challengeData) { return buildFunctions.hasOwnProperty(challengeType); } -export async function buildChallenge(challengeData) { +export async function buildChallenge(challengeData, preview = false) { const { challengeType } = challengeData; let build = buildFunctions[challengeType]; if (build) { - return build(challengeData); + return build(challengeData, preview); } throw new Error(`Cannot build challenge of type ${challengeType}`); } @@ -137,8 +137,10 @@ export function buildDOMChallenge({ files, required = [], template = '' }) { })); } -export function buildJSChallenge({ files }) { - const pipeLine = composeFunctions(...transformers); +export function buildJSChallenge({ files }, preview = false) { + const pipeLine = preview + ? composeFunctions(...transformersPreview) + : composeFunctions(...transformers); const finalFiles = Object.keys(files) .map(key => files[key]) .map(pipeLine); diff --git a/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-12-highly-divisible-triangular-number.english.md b/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-12-highly-divisible-triangular-number.english.md index e2a97c4a89..ec923be79e 100644 --- a/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-12-highly-divisible-triangular-number.english.md +++ b/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-12-highly-divisible-triangular-number.english.md @@ -62,44 +62,67 @@ divisibleTriangleNumber(500); - - ## Solution
- ```js function divisibleTriangleNumber(n) { + if (n === 1) return 3; let counter = 1; let triangleNumber = counter++; - function getFactors(num) { - let factors = []; - let possibleFactor = 1; - let sqrt = Math.sqrt(num); + while (noOfFactors(triangleNumber) < n) { + triangleNumber += counter++; + } +return triangleNumber; +} - while (possibleFactor <= sqrt) { - if (num % possibleFactor == 0) { - factors.push(possibleFactor); - var otherPossibleFactor = num / possibleFactor; - if (otherPossibleFactor > possibleFactor) { - factors.push(otherPossibleFactor); - } +function noOfFactors(num) { + const primeFactors = getPrimeFactors(num); + let prod = 1; + for(let p in primeFactors) { + prod *= (primeFactors[p] + 1) + } + 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) { - triangleNumber += counter++; + while(p === 2 && p <= n) { + 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; } ``` diff --git a/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-14-longest-collatz-sequence.english.md b/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-14-longest-collatz-sequence.english.md index a550092b37..3d6ad37cbe 100644 --- a/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-14-longest-collatz-sequence.english.md +++ b/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-14-longest-collatz-sequence.english.md @@ -35,8 +35,8 @@ tests: testString: assert.strictEqual(longestCollatzSequence(46500), 35655); - text: longestCollatzSequence(54512) should return 52527. testString: assert.strictEqual(longestCollatzSequence(54512), 52527); - - text: longestCollatzSequence(1000000) should return 837799. - testString: assert.strictEqual(longestCollatzSequence(1000000), 837799); + - text: longestCollatzSequence(100000) should return 77031. + testString: assert.strictEqual(longestCollatzSequence(100000), 77031); ``` diff --git a/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-7-10001st-prime.english.md b/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-7-10001st-prime.english.md index ab7834db33..436dfc60b0 100644 --- a/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-7-10001st-prime.english.md +++ b/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-7-10001st-prime.english.md @@ -61,21 +61,22 @@ nthPrime(10001); ```js -const nthPrime = (number)=>{ - let pN = 2; - let step = 0; - while (step { + let pN = 2; + let step = 0; + while (step < n) { + let isPrime = true; + let rootN = Math.sqrt(pN); + for (let i = 2; i <= rootN; i++) { + if (!(pN % i)) { isPrime = false; break; } - } - isPrime ? step++ : ''; + } + isPrime ? step++ : ''; pN++; - } - return pN-1; + } + return pN - 1; } ``` diff --git a/curriculum/challenges/english/08-coding-interview-prep/rosetta-code/emirp-primes.english.md b/curriculum/challenges/english/08-coding-interview-prep/rosetta-code/emirp-primes.english.md index 6dd20798cb..0970348726 100644 --- a/curriculum/challenges/english/08-coding-interview-prep/rosetta-code/emirp-primes.english.md +++ b/curriculum/challenges/english/08-coding-interview-prep/rosetta-code/emirp-primes.english.md @@ -31,8 +31,8 @@ tests: testString: assert(typeof emirps === 'function'); - text: emirps(20,true) should return [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: emirps(10000) should return 948349 - testString: assert.deepEqual(emirps(10000), 948349); + - text: emirps(1000) should return 70529 + testString: assert.deepEqual(emirps(1000), 70529); - text: emirps([7700,8000],true) should return [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: emirps([7700,8000],true) should return 11