// opening a file in the editor checks it, and a check runs the file's compile-time code // (immediate functions). the server must not let that code write files, so a file that // tries to leaves nothing behind and the server still answers. // usage: node tests/drivers/lsp_sandbox.js path/to/zclsp // interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const cp = require('child_process'), fs = require('fs'), os = require('os'), path = require('path'); const target = path.join(os.tmpdir(), 'zc-sandbox-' + process.pid); try { fs.unlinkSync(target); } catch (e) {} const src = 'include rt, run;\nimmediate fn evil() { run.next(); let fd = rt.syscall(1, "' + target + '", 1, 0); rt.syscall(4, fd, 65, 1); rt.syscall(2, fd, 0, 0); return 0; }\nevil\nfn main() { return 0; }\n'; const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + b.length + '\r\n\r\n'), b]); }; const input = Buffer.concat([ frame({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { processId: 1, capabilities: {} } }), frame({ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri: 'file:///evil.zc', languageId: 'zc', version: 1, text: src } } }), frame({ jsonrpc: '2.0', id: 3, method: 'textDocument/semanticTokens/full', params: { textDocument: { uri: 'file:///evil.zc' } } }), frame({ jsonrpc: '2.0', method: 'exit' }), ]); const r = cp.spawnSync(process.argv[2], [], { input, timeout: 20000 * TIMEOUT_SCALE }); const wrote = fs.existsSync(target); if (wrote) fs.unlinkSync(target); const answered = r.stdout && r.stdout.includes('"id":3'); const ok = !wrote && answered; console.log((ok ? 'ok ' : 'FAIL ') + 'lsp sandbox: compile-time code cannot write files' + (ok ? '' : ' (wrote: ' + wrote + ', answered: ' + !!answered + ')')); process.exit(ok ? 0 : 1);