Skip to content

Commit

Permalink
Merge pull request #1778 from kujirahand/fix_asyncfn_broken_localvars
Browse files Browse the repository at this point in the history
非同期関数でローカル変数が壊れる問題を修正 #1758
  • Loading branch information
kujirahand authored Nov 1, 2024
2 parents a3c0f8e + 84d9f2c commit 91b3e9a
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 13 deletions.
35 changes: 28 additions & 7 deletions core/src/nako_gen.mts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ export class NakoGen {
// eslint-disable-next-line @typescript-eslint/no-empty-function
fn: () => {},
type: 'func',
asyncFn: t.asyncFn,
asyncFn: false,
isExport: t.isExport
})
funcList.push({ name, node: t })
Expand Down Expand Up @@ -862,7 +862,7 @@ export class NakoGen {
variableDeclarations += indent + '__self.__vars.set(\'引数\', arguments);\n'
}

// ローカル変数を生成 (再帰関数呼び出しで引数の値が壊れる問題がある #1663)
// ローカル変数を生成 (再帰関数呼び出しで引数の値が壊れる問題があるので修正 #1663 / タイミングによって壊れるので修理 #1758)
// 暫定変数__localVarsに現在のローカル変数の値をPUSHし、変数を抜ける時にPOPする)
// 関数として宣言しているが、JS関数となでしこ関数では変数管理の方法が異なるため、完全なローカル変数としては使えない
// 必ず、pushStack/popStack する必要がある
Expand All @@ -874,6 +874,7 @@ export class NakoGen {
popStack += indent + '// POP STACK\n'
popStack += indent + 'self.__vars = __localvars;\n'
popStack += '}\n'

// 宣言済みの名前を保存
const varsDeclared = Array.from(this.varsSet.names.values())
let code = ''
Expand Down Expand Up @@ -929,7 +930,8 @@ export class NakoGen {
}
// パフォーマンスモニタ:ユーザ関数のinject
code += performanceMonitorInjectAtEnd
// ブロックでasyncFnを使ったか

// 名前のある関数で非同期関数であれば、関数にasyncを付与する
if (name && this.usedAsyncFn) {
const f = this.nakoFuncList.get(name)
if (f) { f.asyncFn = true }
Expand Down Expand Up @@ -1565,6 +1567,11 @@ export class NakoGen {
funcCall = `await ${funcCall}`
this.numAsyncFn++
this.usedAsyncFn = true
// 非同期関数の呼び出し前に __self.__vars を待避しておく必要がある (#1758)
const varI = `$nako_i${this.loopId}`
this.loopId++
funcBegin += `const __local_async${varI} = __self.__vars;\n`
funcEnd += `__self.__vars = __local_async${varI};\n`
}
if (res.i === 0 && this.performanceMonitor.systemFunctionBody !== 0) {
let key = funcName
Expand Down Expand Up @@ -1600,9 +1607,9 @@ export class NakoGen {
// 戻り値のない関数の場合
// ------------------------------------
if (funcEnd === '') {
code = `/*戻値のない関数呼出1*/${funcBegin}${funcCall};\n`
code = `/*VOID関数呼出*/${funcBegin}${funcCall};\n`
} else {
code = `/*戻値のない関数呼出2*/${funcBegin}try {\n${indent(funcCall, 1)};\n} finally {\n${indent(funcEnd, 1)}}\n`
code = `/*VOID関数呼出(前後処理付)*/${funcBegin}try {\n${indent(funcCall, 1)};\n} finally {\n${indent(funcEnd, 1)}}\n`
}
} else {
// ------------------------------------
Expand All @@ -1624,7 +1631,20 @@ export class NakoGen {
const funcCallThis = `(${funcObj}).call(this)`
code = `/* funcCallThis1 */${funcCallThis}`
} else { // つまり、pure=falseの場合
code = `/* funcCallThis2 */(${funcDef}(){\n${indent(`${funcBegin}try {\n${indent(`return ${sorePrefex}${funcCall}${sorePostfix};`, 1)}\n} finally {\n${indent(funcEnd, 1)}}`, 1)}}).call(this)`
const varI = `$nako_i${this.loopId}`
this.loopId++
code = `/* funcCallThis2 */(${funcDef}(){\n` +
indent(funcBegin, 1) + '\n' +
indent('try {', 1) + '\n' +
indent(`let ${varI} = ${funcCall};`, 2) + '\n' +
indent(`return ${sorePrefex}${varI}${sorePostfix};`, 2) + '\n' +
indent('} finally {', 2) + '\n' +
indent(funcEnd, 1) + '\n' +
indent('}', 1) + '\n' +
'}).call(this)'
if (func.asyncFn) {
code = `await (${code})`
}
}
}
// ...して
Expand Down Expand Up @@ -2072,7 +2092,6 @@ ${syncMain}(__self)
// --- 生成したコードの簡単な整形 ---
js = cleanGeneratedCode(js)
// デバッグメッセージ
com.getLogger().trace('--- generate ---\n' + js)
let codeImportFiles = ''
const importNames = []
for (const f of opt.importFiles) {
Expand All @@ -2092,6 +2111,8 @@ ${initCode}
${js}
// </runtimeEnvCode>
`
com.getLogger().trace('--- generate::jsInit ---\n' + jsInit)
com.getLogger().trace('--- generate::js ---\n' + js)
return {
// なでしこの実行環境ありの場合(thisが有効)
runtimeEnv: runtimeEnvCode,
Expand Down
7 changes: 5 additions & 2 deletions core/src/plugin_system.mts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { NakoRuntimeError } from './nako_errors.mjs'
import { NakoSystem } from './plugin_api.mjs'

Expand Down Expand Up @@ -167,6 +168,7 @@ export default {
}
}
// eval function #1733 - 互換性を優先するため、direct evalを使うことに
// eslint-disable-next-line @typescript-eslint/no-unused-vars
sys.__evalJS = (src: string, sys?: NakoSystem) => {
try {
return eval(src)
Expand Down Expand Up @@ -1853,6 +1855,7 @@ export default {
type: 'func',
josi: [['から', 'の'], ['を']],
pure: true,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
fn: function (a: any, i: any, sys: any) {
// 文字列のとき
if (typeof a === 'string') {
Expand Down Expand Up @@ -2925,7 +2928,7 @@ export default {
type: 'func',
josi: [],
pure: true,
asyncFn: true,
asyncFn: false,
fn: function (sys: NakoSystem) {
return sys.josiList
}
Expand All @@ -2934,7 +2937,7 @@ export default {
type: 'func',
josi: [],
pure: true,
asyncFn: true,
asyncFn: false,
fn: function (sys: NakoSystem) {
return sys.reservedWords
}
Expand Down
5 changes: 3 additions & 2 deletions core/test/calc_test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { NakoCompiler } from '../src/nako3.mjs'
describe('calc_test.js', async () => {
const cmp = async (/** @type {string} */ code, /** @type {string} */ res) => {
const nako = new NakoCompiler()
assert.strictEqual((await nako.runAsync(code)).log, res)
assert.strictEqual((await nako.runAsync(code, 'main.nako3')).log, res)
}
const errorTest = async (/** @type {string} */ code, /** @type {string} */ errorType, /** @type {string} */ partOfErrorStr) => {
const nako = new NakoCompiler()
try {
await nako.runAsync(code)
await nako.runAsync(code, 'main.nako3')
} catch (err) {
assert.strictEqual(errorType, err.type)
assert.strictEqual(err.msg.indexOf(partOfErrorStr) >= 0, true)
Expand Down Expand Up @@ -236,4 +236,5 @@ describe('calc_test.js', async () => {
await cmp('1234567890123456789n<<60nを表示', '1423359869420436337641010675071320064')
await cmp('123456789012345678901234567890123456789n>>60nを表示', '107081695084215790682')
})

})
41 changes: 39 additions & 2 deletions core/test/func_call.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/* eslint-disable no-irregular-whitespace */
/* eslint-disable no-undef */
import assert from 'assert'
import { NakoCompiler } from '../src/nako3.mjs'

describe('関数呼び出しテスト', async () => {
const cmp = async (/** @type {string} */ code, /** @type {string} */ res) => {
const nako = new NakoCompiler()
nako.logger.debug('code=' + code)
nako.getLogger().debug('code=' + code)
const g = await nako.runAsync(code, 'main.nako3')
if (code.indexOf('秒待') >= 0) {
await forceWait(200)
Expand All @@ -14,7 +15,7 @@ describe('関数呼び出しテスト', async () => {
}
// 強制的にミリ秒待機
function forceWait(/** @type {number} */ms) {
return /** @type {Promise<void>} */(new Promise((resolve, reject) => {
return /** @type {Promise<void>} */(new Promise((resolve) => {
setTimeout(() => { resolve() }, ms);
}));
}
Expand Down Expand Up @@ -190,4 +191,40 @@ describe('関数呼び出しテスト', async () => {
`
await cmp(code, 'CCCBBBCCCAAA')
})

it('後方で宣言した関数がasyncFnだったときその関数が正しく実行できない(1) #1758', async () => {
const code = `
●test1():
 xとは変数= 1
 0.05秒待つ
 xを表示
●test2():
 xとは変数= 2
 0.05秒待つ
 xを表示
0.01秒後には:
 test1
0.05秒後には:
 test2`
await cmp(code, '1\n2')
})
it('後方で宣言した関数がasyncFnだったときその関数が正しく実行できない(2) #1758', async () => {
const code = `
0.1秒後には:
 Aとは変数 = 「A」
 関数A
 Aを表示 //→undefined
●関数Aとは:
 Bとは変数 = 「B」
 関数B
 Bを表示 //→undefined
●関数Bとは:
 もし0ならば:
  0.01秒待つ //(待たない)
`
await cmp(code, 'B\nA')
})
})

0 comments on commit 91b3e9a

Please sign in to comment.