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:
committed by
mrugesh
parent
f5360e9393
commit
01b37f664f
10
client/package-lock.json
generated
10
client/package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
];
|
];
|
||||||
|
@ -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)));
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
Reference in New Issue
Block a user