mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
draft: hack?
This commit is contained in:
1 parent
cd3a426ef8
commit
8b956575ee
10 files changed
+213
-10
No files matched your search
@@ -24,5 +24,5 @@ export async function compileChecker(checkerType: string, checker: string, copyI
|
|||||||
if (!checkers[checkerType]) { throw new SystemError(`未知比较器类型:${checkerType}`); }
|
if (!checkers[checkerType]) { throw new SystemError(`未知比较器类型:${checkerType}`); }
|
||||||
const file = await fs.readFile(checker);
|
const file = await fs.readFile(checker);
|
||||||
// TODO cache compiled checker
|
// TODO cache compiled checker
|
||||||
await compile(checker.split('.')[1], file.toString(), 'checker', copyIn);
|
return await compile(checker.split('.')[1], file.toString(), 'checker', copyIn);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
// Not implemented
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import { STATUS } from 'hydrooj/dist/model/builtin';
|
||||||
|
import { CompileError } from '../error';
|
||||||
|
import { copyInDir, parseFilename } from '../utils';
|
||||||
|
import { run } from '../sandbox';
|
||||||
|
import compile from '../compile';
|
||||||
|
import signals from '../signals';
|
||||||
|
import { check, compileChecker } from '../check';
|
||||||
|
|
||||||
|
export const judge = async (ctx) => {
|
||||||
|
if (ctx.config.template) {
|
||||||
|
if (ctx.config.template[ctx.lang]) {
|
||||||
|
const tpl = ctx.config.template[ctx.lang];
|
||||||
|
ctx.code = tpl[0] + ctx.code + tpl[1];
|
||||||
|
} else throw new CompileError('Language not supported by provided templates');
|
||||||
|
}
|
||||||
|
ctx.next({ status: STATUS.STATUS_COMPILING });
|
||||||
|
if (!ctx.config.validator || !ctx.config.std) throw new CompileError('config.validator or config.std is missing.');
|
||||||
|
const [execute, executeValidator, executeStd, executeChecker] = await Promise.all([
|
||||||
|
// UserProgram
|
||||||
|
(async () => {
|
||||||
|
const copyIn = {};
|
||||||
|
for (const file of ctx.config.user_extra_files) {
|
||||||
|
copyIn[parseFilename(file)] = { src: file };
|
||||||
|
}
|
||||||
|
return await compile(ctx.lang, ctx.code, 'code', copyIn, ctx.next);
|
||||||
|
})(),
|
||||||
|
// Validator
|
||||||
|
(async () => {
|
||||||
|
const copyIn = {};
|
||||||
|
for (const file of ctx.config.judge_extra_files) {
|
||||||
|
copyIn[parseFilename(file)] = { src: file };
|
||||||
|
}
|
||||||
|
const file = await fs.readFile(ctx.config.validator);
|
||||||
|
return await compile(parseFilename(ctx.config.validator).split('.')[1], file.toString(), 'validator', copyIn);
|
||||||
|
})(),
|
||||||
|
// Std
|
||||||
|
(async () => {
|
||||||
|
const copyIn = {};
|
||||||
|
for (const file of ctx.config.user_extra_files) {
|
||||||
|
copyIn[parseFilename(file)] = { src: file };
|
||||||
|
}
|
||||||
|
const file = await fs.readFile(ctx.config.std);
|
||||||
|
return await compile(parseFilename(ctx.config.std).split('.')[1], file.toString(), 'std', copyIn);
|
||||||
|
})(),
|
||||||
|
// Checker
|
||||||
|
(async () => {
|
||||||
|
if (!ctx.config.checker_type || ctx.config.checker_type === 'default') return null;
|
||||||
|
const copyIn = {};
|
||||||
|
for (const file of ctx.config.judge_extra_files) {
|
||||||
|
copyIn[parseFilename(file)] = { src: file };
|
||||||
|
}
|
||||||
|
return await compileChecker(
|
||||||
|
ctx.config.checker_type,
|
||||||
|
ctx.config.checker,
|
||||||
|
copyIn,
|
||||||
|
);
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
ctx.clean.push(execute.clean, executeValidator.clean, executeStd.clean, executeChecker.clean);
|
||||||
|
ctx.next({ status: STATUS.STATUS_JUDGING, progress: 0 });
|
||||||
|
const { filename } = ctx.config;
|
||||||
|
const input = path.resolve(ctx.tmpdir, 'input');
|
||||||
|
await fs.writeFile(input, ctx.config.hack);
|
||||||
|
if (filename) execute.copyIn[`${filename}.in`] = { src: input };
|
||||||
|
const copyOut = filename ? [`${filename}.out`] : [];
|
||||||
|
const stdin = filename ? null : input;
|
||||||
|
const stdout = path.resolve(ctx.tmpdir, 'user.out');
|
||||||
|
const stderr = path.resolve(ctx.tmpdir, 'user.err');
|
||||||
|
const res = await run(
|
||||||
|
ctx.execute.execute.replace(/\$\{name\}/g, 'code'),
|
||||||
|
{
|
||||||
|
stdin,
|
||||||
|
stdout: filename ? null : stdout,
|
||||||
|
stderr,
|
||||||
|
copyIn: execute.copyIn,
|
||||||
|
copyOut,
|
||||||
|
time_limit_ms: 1000,
|
||||||
|
memory_limit_mb: 256,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const { code, time_usage_ms, memory_usage_kb } = res;
|
||||||
|
let { status } = res;
|
||||||
|
if (res.files[`${filename}.out`] || !fs.existsSync(stdout)) {
|
||||||
|
fs.writeFileSync(stdout, res.files[`${filename}.out`] || '');
|
||||||
|
}
|
||||||
|
let message = '';
|
||||||
|
if (status === STATUS.STATUS_ACCEPTED) {
|
||||||
|
if (time_usage_ms > 1000) {
|
||||||
|
status = STATUS.STATUS_TIME_LIMIT_EXCEEDED;
|
||||||
|
} else if (memory_usage_kb > 256 * 1024) {
|
||||||
|
status = STATUS.STATUS_MEMORY_LIMIT_EXCEEDED;
|
||||||
|
} else {
|
||||||
|
[status, , message] = await check({
|
||||||
|
copyIn: copyInDir(path.resolve(ctx.tmpdir, 'checker')),
|
||||||
|
stdin: input,
|
||||||
|
stdout,
|
||||||
|
user_stdout: stdout,
|
||||||
|
user_stderr: stderr,
|
||||||
|
checker: ctx.config.checker,
|
||||||
|
checker_type: ctx.config.checker_type,
|
||||||
|
score: 100,
|
||||||
|
detail: ctx.config.detail,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (status === STATUS.STATUS_RUNTIME_ERROR && code) {
|
||||||
|
status = STATUS.STATUS_RUNTIME_ERROR;
|
||||||
|
if (code < 32) message = signals[code];
|
||||||
|
else message = `您的程序返回了 ${code}.`;
|
||||||
|
}
|
||||||
|
ctx.next({
|
||||||
|
status: STATUS.STATUS_JUDGING,
|
||||||
|
case: {
|
||||||
|
status,
|
||||||
|
score: 0,
|
||||||
|
time_ms: time_usage_ms,
|
||||||
|
memory_kb: memory_usage_kb,
|
||||||
|
message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.stat.done = new Date();
|
||||||
|
ctx.next({ message: JSON.stringify(ctx.stat) });
|
||||||
|
ctx.end({
|
||||||
|
status,
|
||||||
|
score: ctx.total_score,
|
||||||
|
time_ms: Math.floor(ctx.total_time_usage_ms * 1000000) / 1000000,
|
||||||
|
memory_kb: ctx.total_memory_usage_kb,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -2,7 +2,8 @@ import * as def from './default';
|
|||||||
import * as interactive from './interactive';
|
import * as interactive from './interactive';
|
||||||
import * as run from './run';
|
import * as run from './run';
|
||||||
import * as submit_answer from './submit_answer';
|
import * as submit_answer from './submit_answer';
|
||||||
|
import * as hack from './hack';
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
default: def, interactive, run, submit_answer,
|
default: def, interactive, run, submit_answer, hack,
|
||||||
}
|
}
|
||||||
@@ -210,6 +210,7 @@ async function postInit() {
|
|||||||
tmpfs.mount(this.tmpdir, '64m');
|
tmpfs.mount(this.tmpdir, '64m');
|
||||||
console.log(`Submission: ${this.rid}`, { pid: this.pid });
|
console.log(`Submission: ${this.rid}`, { pid: this.pid });
|
||||||
if (this.config.input) await this.run();
|
if (this.config.input) await this.run();
|
||||||
|
else if (this.config.hack) await this.hack();
|
||||||
else await this.submission();
|
else await this.submission();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof CompileError) {
|
if (e instanceof CompileError) {
|
||||||
@@ -249,6 +250,19 @@ async function postInit() {
|
|||||||
await judge[this.config.type || 'default'].judge(this);
|
await judge[this.config.type || 'default'].judge(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async hack() {
|
||||||
|
this.stat.cache_start = new Date();
|
||||||
|
this.folder = await cacheOpen(this.domainId, this.pid, this.data);
|
||||||
|
this.stat.read_cases = new Date();
|
||||||
|
this.config = await readYamlCases(
|
||||||
|
this.folder,
|
||||||
|
this.config,
|
||||||
|
{ next: this.next },
|
||||||
|
);
|
||||||
|
this.stat.judge = new Date();
|
||||||
|
await judge.hack.judge(this);
|
||||||
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
this.stat.judge = new Date();
|
this.stat.judge = new Date();
|
||||||
await judge.run.judge(this);
|
await judge.run.judge(this);
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ SETTINGS_PRIVACY.allowRegisteredUsers
|
|||||||
model.setting.ui.name
|
model.setting.ui.name
|
||||||
pdoc.secretConfig?
|
pdoc.secretConfig?
|
||||||
message.markAsRead
|
message.markAsRead
|
||||||
基于document的model难以lint,建议拆分
|
|
||||||
检查多余的索引
|
检查多余的索引
|
||||||
+hack
|
+hack
|
||||||
评测队列优先级系统
|
评测队列优先级系统
|
||||||
讨论浏览数量显示
|
讨论浏览数量显示
|
||||||
|
解离mail模块
|
||||||
@@ -2,6 +2,7 @@ import { ObjectID } from 'mongodb';
|
|||||||
import { PermissionError, RecordNotFoundError } from '../error';
|
import { PermissionError, RecordNotFoundError } from '../error';
|
||||||
import { PERM, CONSTANT } from '../model/builtin';
|
import { PERM, CONSTANT } from '../model/builtin';
|
||||||
import * as problem from '../model/problem';
|
import * as problem from '../model/problem';
|
||||||
|
import * as file from '../model/file';
|
||||||
import * as record from '../model/record';
|
import * as record from '../model/record';
|
||||||
import * as contest from '../model/contest';
|
import * as contest from '../model/contest';
|
||||||
import * as user from '../model/user';
|
import * as user from '../model/user';
|
||||||
@@ -11,6 +12,7 @@ import {
|
|||||||
Route, Handler, Connection, ConnectionHandler, Types, param,
|
Route, Handler, Connection, ConnectionHandler, Types, param,
|
||||||
} from '../service/server';
|
} from '../service/server';
|
||||||
import { Rdoc } from '../interface';
|
import { Rdoc } from '../interface';
|
||||||
|
import { streamToBuffer } from '../utils';
|
||||||
|
|
||||||
const RecordHandler = contest.ContestHandlerMixin(Handler);
|
const RecordHandler = contest.ContestHandlerMixin(Handler);
|
||||||
|
|
||||||
@@ -84,8 +86,51 @@ class RecordDetailHandler extends RecordHandler {
|
|||||||
pdoc,
|
pdoc,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@param('rid', Types.ObjectID)
|
||||||
|
async postRejudge(domainId: string, rid: ObjectID) {
|
||||||
|
this.checkPerm(PERM.PERM_REJUDGE);
|
||||||
|
const rdoc = await record.get(domainId, rid);
|
||||||
|
if (rdoc) {
|
||||||
|
await record.reset(domainId, rid, true);
|
||||||
|
await record.judge(domainId, rid);
|
||||||
|
}
|
||||||
|
this.back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RecordHackHandler extends Handler {
|
||||||
|
@param('rid', Types.ObjectID)
|
||||||
|
@param('ufid', Types.ObjectID)
|
||||||
|
async get(domainId: string, rid: ObjectID, ufid?: ObjectID) {
|
||||||
|
if (ufid) {
|
||||||
|
const stream = await file.get(ufid);
|
||||||
|
const buf = await streamToBuffer(stream);
|
||||||
|
const input = buf.toString();
|
||||||
|
await this.post(domainId, rid, input);
|
||||||
|
} else this.response.template = 'record_hack.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
@param('rid', Types.ObjectID)
|
||||||
|
@param('input', Types.String)
|
||||||
|
async post(domainId: string, rid: ObjectID, input: string) {
|
||||||
|
const rdoc = await record.get(domainId, rid);
|
||||||
|
if (!rdoc) throw new RecordNotFoundError(domainId, rid);
|
||||||
|
const newRid = await record.add(
|
||||||
|
domainId, rdoc.pid, this.user._id,
|
||||||
|
rdoc.lang, rdoc.code, true,
|
||||||
|
{ hack: input },
|
||||||
|
);
|
||||||
|
bus.boardcast('record/change', rdoc);
|
||||||
|
this.response.body = { rid: newRid };
|
||||||
|
this.response.redirect = this.url('record_detail', { rid: newRid });
|
||||||
|
// TODO handle hack(modify testdata, rejudge problem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
class RecordRejudgeHandler extends Handler {
|
class RecordRejudgeHandler extends Handler {
|
||||||
@param('rid', Types.ObjectID)
|
@param('rid', Types.ObjectID)
|
||||||
async post(domainId: string, rid: ObjectID) {
|
async post(domainId: string, rid: ObjectID) {
|
||||||
@@ -183,6 +228,7 @@ class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(Connecti
|
|||||||
export async function apply() {
|
export async function apply() {
|
||||||
Route('record_main', '/record', RecordListHandler);
|
Route('record_main', '/record', RecordListHandler);
|
||||||
Route('record_detail', '/record/:rid', RecordDetailHandler);
|
Route('record_detail', '/record/:rid', RecordDetailHandler);
|
||||||
|
Route('record_hack', '/record/:rid/hack', RecordHackHandler);
|
||||||
Route('record_rejudge', '/record/:rid/rejudge', RecordRejudgeHandler);
|
Route('record_rejudge', '/record/:rid/rejudge', RecordRejudgeHandler);
|
||||||
Connection('record_conn', '/record-conn', RecordMainConnectionHandler);
|
Connection('record_conn', '/record-conn', RecordMainConnectionHandler);
|
||||||
Connection('record_detail_conn', '/record-detail-conn', RecordDetailConnectionHandler);
|
Connection('record_detail_conn', '/record-detail-conn', RecordDetailConnectionHandler);
|
||||||
|
|||||||
@@ -199,12 +199,18 @@ export interface TestCase {
|
|||||||
message: string,
|
message: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HackConfig {
|
||||||
|
hack: string,
|
||||||
|
}
|
||||||
|
|
||||||
export interface PretestConfig {
|
export interface PretestConfig {
|
||||||
time?: string,
|
time?: string,
|
||||||
memory?: string,
|
memory?: string,
|
||||||
input: string,
|
input: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RunConfig = HackConfig | PretestConfig;
|
||||||
|
|
||||||
export interface ContestInfo {
|
export interface ContestInfo {
|
||||||
type: 30 | 60,
|
type: 30 | 60,
|
||||||
tid: ObjectID,
|
tid: ObjectID,
|
||||||
@@ -229,7 +235,7 @@ export interface Rdoc {
|
|||||||
status: number,
|
status: number,
|
||||||
hidden: boolean,
|
hidden: boolean,
|
||||||
progress?: number,
|
progress?: number,
|
||||||
config?: PretestConfig,
|
config?: RunConfig,
|
||||||
contest?: ContestInfo,
|
contest?: ContestInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -550,6 +550,7 @@ export const STATUS = {
|
|||||||
STATUS_SYSTEM_ERROR: 8,
|
STATUS_SYSTEM_ERROR: 8,
|
||||||
STATUS_CANCELED: 9,
|
STATUS_CANCELED: 9,
|
||||||
STATUS_ETC: 10,
|
STATUS_ETC: 10,
|
||||||
|
STATUS_HACKED: 11,
|
||||||
STATUS_JUDGING: 20,
|
STATUS_JUDGING: 20,
|
||||||
STATUS_COMPILING: 21,
|
STATUS_COMPILING: 21,
|
||||||
STATUS_FETCHED: 22,
|
STATUS_FETCHED: 22,
|
||||||
@@ -568,6 +569,7 @@ export const STATUS_TEXTS = {
|
|||||||
[STATUS.STATUS_SYSTEM_ERROR]: 'System Error',
|
[STATUS.STATUS_SYSTEM_ERROR]: 'System Error',
|
||||||
[STATUS.STATUS_CANCELED]: 'Cancelled',
|
[STATUS.STATUS_CANCELED]: 'Cancelled',
|
||||||
[STATUS.STATUS_ETC]: 'Unknown Error',
|
[STATUS.STATUS_ETC]: 'Unknown Error',
|
||||||
|
[STATUS.STATUS_HACKED]: 'Hacked',
|
||||||
[STATUS.STATUS_JUDGING]: 'Running',
|
[STATUS.STATUS_JUDGING]: 'Running',
|
||||||
[STATUS.STATUS_COMPILING]: 'Compiling',
|
[STATUS.STATUS_COMPILING]: 'Compiling',
|
||||||
[STATUS.STATUS_FETCHED]: 'Fetched',
|
[STATUS.STATUS_FETCHED]: 'Fetched',
|
||||||
@@ -586,6 +588,7 @@ export const STATUS_CODES = {
|
|||||||
8: 'fail',
|
8: 'fail',
|
||||||
9: 'ignored',
|
9: 'ignored',
|
||||||
10: 'fail',
|
10: 'fail',
|
||||||
|
11: 'fail',
|
||||||
20: 'progress',
|
20: 'progress',
|
||||||
21: 'progress',
|
21: 'progress',
|
||||||
22: 'progress',
|
22: 'progress',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { STATUS } from './builtin';
|
|||||||
import * as task from './task';
|
import * as task from './task';
|
||||||
import * as problem from './problem';
|
import * as problem from './problem';
|
||||||
import {
|
import {
|
||||||
Rdoc, TestCase, PretestConfig, ContestInfo, ProblemConfig,
|
Rdoc, TestCase, RunConfig, ContestInfo, ProblemConfig,
|
||||||
} from '../interface';
|
} from '../interface';
|
||||||
import * as db from '../service/db';
|
import * as db from '../service/db';
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ export async function get(domainId: string, _id: ObjectID): Promise<Rdoc | null>
|
|||||||
|
|
||||||
export async function judge(domainId: string, rid: ObjectID) {
|
export async function judge(domainId: string, rid: ObjectID) {
|
||||||
const rdoc = await get(domainId, rid);
|
const rdoc = await get(domainId, rid);
|
||||||
let config: ProblemConfig = rdoc.config;
|
let config: RunConfig | ProblemConfig = rdoc.config;
|
||||||
if (!config) {
|
if (!config) {
|
||||||
const pdoc = await problem.get(domainId, rdoc.pid);
|
const pdoc = await problem.get(domainId, rdoc.pid);
|
||||||
config = pdoc?.config || {};
|
config = pdoc?.config || {};
|
||||||
@@ -73,7 +73,7 @@ export async function judge(domainId: string, rid: ObjectID) {
|
|||||||
|
|
||||||
export async function add(
|
export async function add(
|
||||||
domainId: string, pid: number, uid: number,
|
domainId: string, pid: number, uid: number,
|
||||||
lang: string, code: string, addTask: boolean, contestOrConfig?: ContestInfo | PretestConfig,
|
lang: string, code: string, addTask: boolean, contestOrConfig?: ContestInfo | RunConfig,
|
||||||
) {
|
) {
|
||||||
const data: Rdoc = {
|
const data: Rdoc = {
|
||||||
status: STATUS.STATUS_WAITING,
|
status: STATUS.STATUS_WAITING,
|
||||||
@@ -98,7 +98,7 @@ export async function add(
|
|||||||
if ((contestOrConfig as ContestInfo).type) {
|
if ((contestOrConfig as ContestInfo).type) {
|
||||||
data.contest = contestOrConfig as ContestInfo;
|
data.contest = contestOrConfig as ContestInfo;
|
||||||
} else {
|
} else {
|
||||||
data.config = contestOrConfig as PretestConfig;
|
data.config = contestOrConfig as RunConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const res = await coll.insertOne(data);
|
const res = await coll.insertOne(data);
|
||||||
|
|||||||
@@ -170,7 +170,8 @@ export function param(name: string, ...args: any): MethodDecorator {
|
|||||||
if (!target.__param[target.constructor.name][funcName]) {
|
if (!target.__param[target.constructor.name][funcName]) {
|
||||||
target.__param[target.constructor.name][funcName] = [{ name: 'domainId', type: 'string' }];
|
target.__param[target.constructor.name][funcName] = [{ name: 'domainId', type: 'string' }];
|
||||||
const originalMethod = obj.value;
|
const originalMethod = obj.value;
|
||||||
obj.value = function validate(rawArgs: any) {
|
obj.value = function validate(rawArgs: any, ...extra: any[]) {
|
||||||
|
if (typeof rawArgs === 'string' || extra.length) return originalMethod.call(this, rawArgs, ...extra);
|
||||||
const c = [];
|
const c = [];
|
||||||
const arglist: ParamOption[] = this.__param[target.constructor.name][funcName];
|
const arglist: ParamOption[] = this.__param[target.constructor.name][funcName];
|
||||||
for (const item of arglist) {
|
for (const item of arglist) {
|
||||||
|
|||||||
Reference in new issue
Block a user