diff --git a/.editorconfig b/.editorconfig index ba49e3c23..f0486255f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,3 +3,12 @@ root = true [*] indent_style = tab indent_size = 4 +end_of_line = lf + +[*.y{,a}ml] +indent_style = space +indent_size = 2 + +[{Dockerfile,*.py,judge_client,*.{c,h}{pp,}}] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore index b013f6e36..94ddef51f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ uoj_data/ .php-cs-fixer.cache .config.local.php +shell.nix diff --git a/docker-compose.yml b/docker-compose.yml index 7dbae208b..3ac5603f8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3' - services: uoj-db: image: ghcr.io/universaloj/uoj-db:latest @@ -13,27 +11,22 @@ services: environment: - MYSQL_DATABASE=app_uoj233 - MYSQL_ROOT_PASSWORD=root - - uoj-judger: - image: ghcr.io/universaloj/uoj-judger:latest - build: - context: ./judger/ - dockerfile: Dockerfile - container_name: uoj-judger - restart: always - stdin_open: true - tty: true - cap_add: - - SYS_PTRACE - volumes: - - ./uoj_data/judger/log:/opt/uoj_judger/log - environment: - - UOJ_PROTOCOL=http - - UOJ_HOST=uoj-web - - JUDGER_NAME=compose_judger - - JUDGER_PASSWORD=_judger_password_ - - SOCKET_PORT=2333 - - SOCKET_PASSWORD=_judger_socket_password_ + healthcheck: + test: + [ + "CMD", + "mysqladmin", + "ping", + "-h", + "localhost", + "-u", + "root", + "-p$$MYSQL_ROOT_PASSWORD", + ] + interval: 5s + timeout: 3s + retries: 5 + start_period: 30s uoj-web: image: ghcr.io/universaloj/uoj-web:latest @@ -47,8 +40,8 @@ services: cap_add: - SYS_PTRACE depends_on: - - uoj-db - - uoj-judger + uoj-db: + condition: service_healthy volumes: - ./uoj_data/web/data:/var/uoj_data - ./uoj_data/web/storage:/opt/uoj/web/app/storage @@ -68,3 +61,33 @@ services: - SALT_2=_salt_2_ - SALT_3=_salt_3_ - UOJ_PROTOCOL=http + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/"] + interval: 5s + timeout: 3s + retries: 5 + start_period: 30s + + uoj-judger: + image: ghcr.io/universaloj/uoj-judger:latest + build: + context: ./judger/ + dockerfile: Dockerfile + container_name: uoj-judger + restart: always + stdin_open: true + tty: true + cap_add: + - SYS_PTRACE + depends_on: + uoj-web: + condition: service_healthy + volumes: + - ./uoj_data/judger/log:/opt/uoj_judger/log + environment: + - UOJ_PROTOCOL=http + - UOJ_HOST=uoj-web + - JUDGER_NAME=compose_judger + - JUDGER_PASSWORD=_judger_password_ + - SOCKET_PORT=2333 + - SOCKET_PASSWORD=_judger_socket_password_ diff --git a/judger/.dockerignore b/judger/.dockerignore new file mode 100644 index 000000000..8e535c53b --- /dev/null +++ b/judger/.dockerignore @@ -0,0 +1,19 @@ +.conf.json +log/* +!/log/.gitkeep +uoj_judger/builtin/checker/* +!uoj_judger/builtin/checker/*.cpp +!uoj_judger/builtin/checker/*.h +uoj_judger/builtin/judger/* +!uoj_judger/builtin/judger/*.cpp +!uoj_judger/builtin/judger/*.h +uoj_judger/data +uoj_judger/main_judger +uoj_judger/run/* +!uoj_judger/run/*.cpp +!uoj_judger/run/*.h +uoj_judger/work/* +uoj_judger/result/* +!uoj_judger/work/.gitkeep +!uoj_judger/result/.gitkeep + diff --git a/judger/.gitignore b/judger/.gitignore index d89c3c004..8e535c53b 100644 --- a/judger/.gitignore +++ b/judger/.gitignore @@ -8,7 +8,6 @@ uoj_judger/builtin/judger/* !uoj_judger/builtin/judger/*.cpp !uoj_judger/builtin/judger/*.h uoj_judger/data -uoj_judger/include/uoj_work_path.h uoj_judger/main_judger uoj_judger/run/* !uoj_judger/run/*.cpp diff --git a/judger/Dockerfile b/judger/Dockerfile index 2b1d5786a..1d614536f 100644 --- a/judger/Dockerfile +++ b/judger/Dockerfile @@ -1,27 +1,40 @@ -FROM ubuntu:20.04 +FROM ubuntu:24.04 MAINTAINER Baoshuo -LABEL org.opencontainers.image.source=https://github.com/UniversalOJ/UOJ-System + +LABEL org.opencontainers.image.source="https://github.com/UniversalOJ/UOJ-System" LABEL org.opencontainers.image.description="UOJ Judger" -LABEL org.opencontainers.image.licenses=MIT +LABEL org.opencontainers.image.licenses="MIT" -SHELL ["/bin/bash", "-c"] +ARG CLONE_ADDFLAG ENV DEBIAN_FRONTEND=noninteractive -ARG CLONE_ADDFLAG +ENV LANG=C.UTF-8 +ENV TZ=Asia/Shanghai -RUN apt-get update && \ - apt-get install -y --no-install-recommends gnupg ca-certificates apt-transport-https && \ +RUN \ apt-get update && \ - for pkg in git vim ntp zip unzip curl wget build-essential fp-compiler python python3 python3-requests openjdk-8-jdk openjdk-11-jdk tzdata; do \ - cnt=10 && \ - while ! apt-get install -y "$pkg"; do \ - if [ $cnt -le 0 ]; then \ - echo "Failed to install $pkg" && \ - exit 1; \ - fi; \ - cnt=$((cnt - 1)); \ - done; \ - done + apt-get install -y --no-install-recommends \ + git vim ntp zip unzip curl wget make dpkg-dev ca-certificates \ + fp-compiler \ + python3 python3-requests \ + openjdk-21-jdk-headless \ + gcc-14 g++-14 \ + libseccomp-dev \ + tzdata && \ + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 && \ + update-alternatives --install /usr/bin/cpp cpp-bin /usr/bin/cpp-14 100 && \ + cd /usr/src && \ + wget https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tgz && \ + tar xzf Python-2.7.18.tgz && \ + cd Python-2.7.18 && \ + ./configure && \ + make -j"$(nproc)" && \ + make install && \ + cd / && \ + rm -rf /usr/src/Python-2.7.18.tgz /usr/src/Python-2.7.18 && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* ADD . /opt/uoj_judger WORKDIR /opt/uoj_judger @@ -29,6 +42,5 @@ WORKDIR /opt/uoj_judger # Install environment and set startup script RUN sh install.sh -p && sh install.sh -d -ENV LANG=C.UTF-8 TZ=Asia/Shanghai EXPOSE 2333 -CMD /opt/up +CMD ["/opt/up"] diff --git a/judger/install.sh b/judger/install.sh index 5300fc016..909850144 100644 --- a/judger/install.sh +++ b/judger/install.sh @@ -15,18 +15,12 @@ UOJEOF #Add judger user adduser judger --gecos "" --disabled-password #Set uoj_data path - mkdir /var/uoj_data_copy && chown judger /var/uoj_data_copy + mkdir /var/uoj_data_copy && chown judger:judger /var/uoj_data_copy #Compile uoj_judger and set runtime chown -R judger:judger /opt/uoj_judger su judger <uoj_judger/include/uoj_work_path.h </opt/up chmod +x /opt/up diff --git a/judger/judge_client b/judger/judge_client index a27de0c28..a9f69487e 100755 --- a/judger/judge_client +++ b/judger/judge_client @@ -15,7 +15,6 @@ from contextlib import closing import requests -import queue as queue from queue import Queue, Empty taskQ = Queue() @@ -23,415 +22,439 @@ submission = None # path related function + def uoj_url(uri): - return ("%s://%s%s" % (jconf['uoj_protocol'], jconf['uoj_host'], uri)).rstrip('/') -def uoj_judger_path(path = ''): - return "uoj_judger" + path + return ("%s://%s%s" % (jconf["uoj_protocol"], jconf["uoj_host"], uri)).rstrip("/") + + +def uoj_judger_path(path=""): + return "uoj_judger" + path + # os related funciton def clean_up_folder(path): - for f in os.listdir(path): - f_path = os.path.join(path, f) - if os.path.isfile(f_path): - os.unlink(f_path) - else: - shutil.rmtree(f_path) + for f in os.listdir(path): + f_path = os.path.join(path, f) + if os.path.isfile(f_path): + os.unlink(f_path) + else: + shutil.rmtree(f_path) + def execute(cmd): - if os.system(cmd): - raise Exception('failed to execute: %s' % cmd) + if os.system(cmd): + raise Exception("failed to execute: %s" % cmd) + def freopen(f, g): - os.dup2(g.fileno(), f.fileno()) - g.close() + os.dup2(g.fileno(), f.fileno()) + g.close() + # init def init(): - global jconf - os.chdir(os.path.dirname(os.path.realpath(__file__))) - with open('.conf.json', 'r') as fp: - jconf = json.load(fp) - assert 'uoj_protocol' in jconf - assert 'uoj_host' in jconf - assert 'judger_name' in jconf - assert 'judger_password' in jconf - assert 'socket_port' in jconf - assert 'socket_password' in jconf + global jconf + os.chdir(os.path.dirname(os.path.realpath(__file__))) + with open(".conf.json", "r") as fp: + jconf = json.load(fp) + assert "uoj_protocol" in jconf + assert "uoj_host" in jconf + assert "judger_name" in jconf + assert "judger_password" in jconf + assert "socket_port" in jconf + assert "socket_password" in jconf + # socket server def socket_server_loop(): - SOCK_CLOEXEC = 524288 - with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)) as s: - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(('', jconf['socket_port'])) - s.listen(5) - - while True: - try: - conn, addr = s.accept() - with closing(conn) as conn: - data = conn.recv(1024).decode() - assert data != None - task = json.loads(data) - assert task['password'] == jconf['socket_password'] - assert 'cmd' in task - - taskQ.put(task) - - if task['cmd'] == 'stop': - print('the judge client is closing...') - taskQ.join() - conn.sendall(b'ok') - return 'stop' - except Exception: - print('['+time.asctime()+']', 'connection rejected', file=sys.stderr) - traceback.print_exc() - else: - print('['+time.asctime()+']', 'a new task accomplished', file=sys.stderr) + SOCK_CLOEXEC = 524288 + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)) as s: + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(("", jconf["socket_port"])) + s.listen(5) + + while True: + try: + conn, addr = s.accept() + with closing(conn) as conn: + data = conn.recv(1024).decode() + assert data != None + task = json.loads(data) + assert task["password"] == jconf["socket_password"] + assert "cmd" in task + + taskQ.put(task) + + if task["cmd"] == "stop": + print("the judge client is closing...") + taskQ.join() + conn.sendall(b"ok") + return "stop" + except Exception: + print("[" + time.asctime() + "]", "connection rejected", file=sys.stderr) + traceback.print_exc() + else: + print("[" + time.asctime() + "]", "a new task accomplished", file=sys.stderr) + def start_judger_server(): - global socket_server_thread - - print_judge_client_status() - print('hello!', file=sys.stderr) - - socket_server_thread = Thread(target = socket_server_loop) - socket_server_thread.setDaemon(True) - socket_server_thread.start() - - judger_loop() + global socket_server_thread + + print_judge_client_status() + print("hello!", file=sys.stderr) + + socket_server_thread = Thread(target=socket_server_loop) + socket_server_thread.setDaemon(True) + socket_server_thread.start() + + judger_loop() + # report thread def report_loop(): - if 'is_hack' in submission: - return - while not submission_judged: - try: - with open(uoj_judger_path('/result/cur_status.txt'), 'r') as f: - fcntl.flock(f, fcntl.LOCK_SH) - try: - status = f.read(100) - except Exception: - status = None - finally: - fcntl.flock(f, fcntl.LOCK_UN) - - if status != None: - data = {} - data['update-status'] = True - data['id'] = submission['id'] - if 'is_custom_test' in submission: - data['is_custom_test'] = True - data['status'] = status - uoj_interact(data) - time.sleep(0.2) - except Exception: - pass + if "is_hack" in submission: + return + while not submission_judged: + try: + with open(uoj_judger_path("/result/cur_status.txt"), "r") as f: + fcntl.flock(f, fcntl.LOCK_SH) + try: + status = f.read(100) + except Exception: + status = None + finally: + fcntl.flock(f, fcntl.LOCK_UN) + + if status is not None: + data = {} + data["update-status"] = True + data["id"] = submission["id"] + if "is_custom_test" in submission: + data["is_custom_test"] = True + data["status"] = status + uoj_interact(data) + time.sleep(0.2) + except Exception: + pass + # handle task in main thread def handle_task(): - need_restart = False - try: - while True: - task = taskQ.get_nowait() - - if task['cmd'] == 'update': - try: - uoj_download('/judger', 'judger_update.zip') - execute('unzip -o judger_update.zip && cd %s && make clean && make' % uoj_judger_path()) - except: - print("error when update", file=sys.stderr) - if jconf['judger_name'] == 'main_judger': - uoj_sync_judge_client() - need_restart = True - elif task['cmd'] == 'stop': - taskQ.task_done() - socket_server_thread.join() - - print_judge_client_status() - print("goodbye!", file=sys.stderr) - - sys.exit(0) - - taskQ.task_done() - except Empty: - pass - - if need_restart: - os.execl('./judge_client', './judge_client') + need_restart = False + try: + while True: + task = taskQ.get_nowait() + + if task["cmd"] == "update": + try: + uoj_download("/judger", "judger_update.zip") + execute( + "unzip -o judger_update.zip && cd %s && make clean && make" + % uoj_judger_path() + ) + except: + print("error when update", file=sys.stderr) + if jconf["judger_name"] == "main_judger": + uoj_sync_judge_client() + need_restart = True + elif task["cmd"] == "stop": + taskQ.task_done() + socket_server_thread.join() + + print_judge_client_status() + print("goodbye!", file=sys.stderr) + + sys.exit(0) + + taskQ.task_done() + except Empty: + pass + + if need_restart: + os.execl("./judge_client", "./judge_client") + def print_judge_client_status(): - print('[' + time.asctime() + ']', end=' ', file=sys.stderr) - if submission != None: - print(submission, end=' ', file=sys.stderr) - print(file=sys.stderr) + print("[" + time.asctime() + "]", end=" ", file=sys.stderr) + if submission is not None: + print(submission, end=" ", file=sys.stderr) + print(file=sys.stderr) + # interact with uoj_judger def get_judger_result(): - res = {} - with open(uoj_judger_path('/result/result.txt'), 'r') as fres: - res['score'] = 0 - res['time'] = 0 - res['memory'] = 0 - while True: - line = fres.readline() - if line == '': - break - line = line.strip() - if line == 'details': - res['details'] = fres.read() - break - - sp = line.split() - assert len(sp) >= 1 - if sp[0] == 'error': - res['error'] = line[len('error') + 1:] - else: - assert len(sp) == 2 - res[sp[0]] = sp[1] - res['score'] = int(res['score']) - res['time'] = int(res['time']) - res['memory'] = int(res['memory']) - res['status'] = 'Judged' - return res + res = {} + with open(uoj_judger_path("/result/result.txt"), "r") as fres: + res["score"] = 0 + res["time"] = 0 + res["memory"] = 0 + while True: + line = fres.readline() + if line == "": + break + line = line.strip() + if line == "details": + res["details"] = fres.read() + break + + sp = line.split() + assert len(sp) >= 1 + if sp[0] == "error": + res["error"] = line[len("error") + 1 :] + else: + assert len(sp) == 2 + res[sp[0]] = sp[1] + res["score"] = float(res["score"]) + res["time"] = int(res["time"]) + res["memory"] = int(res["memory"]) + res["status"] = "Judged" + return res + def update_problem_data(problem_id, problem_mtime): - try: - if jconf['judger_name'] == 'main_judger': - return - copy_name = uoj_judger_path('/data/%d' % problem_id) - copy_zip_name = uoj_judger_path('/data/%d.zip' % problem_id) - if os.path.isdir(copy_name): - quoted_copy_name = pipes.quote(copy_name) - if os.path.getmtime(copy_name) >= problem_mtime: - execute('touch -a %s' % quoted_copy_name) - return - else: - execute('chmod 700 %s -R && rm -rf %s' % (quoted_copy_name, quoted_copy_name)) - del_list = sorted(os.listdir(uoj_judger_path('/data')), key=lambda fname: os.path.getatime(uoj_judger_path('/data/%s' % fname)))[:-99] - for fname in del_list: - quoted_fname = pipes.quote(uoj_judger_path('/data/%s' % fname)) - os.system('chmod 700 %s -R && rm -rf %s' % (quoted_fname, quoted_fname)) - - uoj_download('/problem/%d' % problem_id, copy_zip_name) - execute('cd %s && unzip -q %d.zip && rm %d.zip && chmod -w %d -R' % (uoj_judger_path('/data'), problem_id, problem_id, problem_id)) - except Exception: - print_judge_client_status() - traceback.print_exc() - raise Exception('failed to update problem data of #%d' % problem_id) - else: - print_judge_client_status() - print('updated problem data of #%d successfully' % problem_id, file=sys.stderr) + try: + if jconf["judger_name"] == "main_judger": + return + copy_name = uoj_judger_path("/data/%d" % problem_id) + copy_zip_name = uoj_judger_path("/data/%d.zip" % problem_id) + if os.path.isdir(copy_name): + quoted_copy_name = pipes.quote(copy_name) + if os.path.getmtime(copy_name) >= problem_mtime: + execute("touch -a %s" % quoted_copy_name) + return + else: + execute("chmod 700 %s -R && rm -rf %s" % (quoted_copy_name, quoted_copy_name)) + del_list = sorted( + os.listdir(uoj_judger_path("/data")), + key=lambda fname: os.path.getatime(uoj_judger_path("/data/%s" % fname)), + )[:-99] + for fname in del_list: + quoted_fname = pipes.quote(uoj_judger_path("/data/%s" % fname)) + os.system("chmod 700 %s -R && rm -rf %s" % (quoted_fname, quoted_fname)) + + uoj_download("/problem/%d" % problem_id, copy_zip_name) + execute( + "cd %s && unzip -q %d.zip && rm %d.zip && chmod -w %d -R" + % (uoj_judger_path("/data"), problem_id, problem_id, problem_id) + ) + except Exception: + print_judge_client_status() + traceback.print_exc() + raise Exception("failed to update problem data of #%d" % problem_id) + else: + print_judge_client_status() + print("updated problem data of #%d successfully" % problem_id, file=sys.stderr) + def judge(): - global report_thread - global submission_judged - - clean_up_folder(uoj_judger_path('/work')) - clean_up_folder(uoj_judger_path('/result')) - update_problem_data(submission['problem_id'], submission['problem_mtime']) - - with open(uoj_judger_path('/work/submission.conf'), 'w') as fconf: - uoj_download(submission['content']['file_name'], uoj_judger_path('/work/all.zip')) - execute("cd %s && unzip -q all.zip && rm all.zip" % pipes.quote(uoj_judger_path('/work'))) - for k, v in submission['content']['config']: - print(k, v, file=fconf) - - if 'is_hack' in submission: - if submission['hack']['input_type'] == 'USE_FORMATTER': - uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input_raw.txt')) - execute('%s <%s >%s' % ( - pipes.quote(uoj_judger_path('/run/formatter')), - pipes.quote(uoj_judger_path('/work/hack_input_raw.txt')), - pipes.quote(uoj_judger_path('/work/hack_input.txt')))) - else: - uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input.txt')) - print('test_new_hack_only on', file=fconf) - elif 'is_custom_test' in submission: - print('custom_test on', file=fconf) - - report_thread = Thread(target = report_loop) - report_thread.setDaemon(True) - - submission_judged = False - report_thread.start() - execute(pipes.quote(uoj_judger_path('/main_judger'))) - submission_judged = True - report_thread.join() - - return get_judger_result() + global report_thread + global submission_judged + + clean_up_folder(uoj_judger_path("/work")) + clean_up_folder(uoj_judger_path("/result")) + update_problem_data(submission["problem_id"], submission["problem_mtime"]) + + with open(uoj_judger_path("/work/submission.conf"), "w") as fconf: + uoj_download(submission["content"]["file_name"], uoj_judger_path("/work/all.zip")) + execute("cd %s && unzip -q all.zip && rm all.zip" % pipes.quote(uoj_judger_path("/work"))) + for k, v in submission["content"]["config"]: + print(k, v, file=fconf) + + if "is_hack" in submission: + if submission["hack"]["input_type"] == "USE_FORMATTER": + uoj_download( + submission["hack"]["input"], uoj_judger_path("/work/hack_input_raw.txt") + ) + execute( + "%s <%s >%s" + % ( + pipes.quote(uoj_judger_path("/run/formatter")), + pipes.quote(uoj_judger_path("/work/hack_input_raw.txt")), + pipes.quote(uoj_judger_path("/work/hack_input.txt")), + ) + ) + else: + uoj_download(submission["hack"]["input"], uoj_judger_path("/work/hack_input.txt")) + print("test_new_hack_only on", file=fconf) + elif "is_custom_test" in submission: + print("custom_test on", file=fconf) + + report_thread = Thread(target=report_loop) + report_thread.setDaemon(True) + + submission_judged = False + report_thread.start() + execute('cd %s && ./main_judger' % (pipes.quote(uoj_judger_path()))) + submission_judged = True + report_thread.join() + + return get_judger_result() + # interact with uoj web server -def uoj_interact(data, files = {}): - data = data.copy() - data.update({ - 'judger_name': jconf['judger_name'], - 'password': jconf['judger_password'] - }) - return requests.post(uoj_url('/judge/submit'), data=data, files=files).text +def uoj_interact(data, files={}): + data = data.copy() + data.update({"judger_name": jconf["judger_name"], "password": jconf["judger_password"]}) + return requests.post(uoj_url("/judge/submit"), data=data, files=files).text + + def uoj_download(uri, filename): - data = { - 'judger_name': jconf['judger_name'], - 'password': jconf['judger_password'] - } - with open(filename, 'wb') as f: - r = requests.post(uoj_url('/judge/download' + uri), data=data, stream=True) - for chunk in r.iter_content(chunk_size=65536): - if chunk: - f.write(chunk) + data = {"judger_name": jconf["judger_name"], "password": jconf["judger_password"]} + with open(filename, "wb") as f: + r = requests.post(uoj_url("/judge/download" + uri), data=data, stream=True) + for chunk in r.iter_content(chunk_size=65536): + if chunk: + f.write(chunk) + + def uoj_sync_judge_client(): - data = { - 'judger_name': jconf['judger_name'], - 'password': jconf['judger_password'] - } - ret = requests.post(uoj_url('/judge/sync-judge-client'), data=data).text - if ret != "ok": - raise Exception('failed to sync judge clients: %s' % ret) - -def send_and_fetch(result = None, fetch_new = True): - global submission - - """send judgement result, and fetch new submission to judge""" - - data = {} - files = {} - - if not fetch_new: - data['fetch_new'] = False - - if result != None: - data['submit'] = True - if 'is_hack' in submission: - data['is_hack'] = True - data['id'] = submission['hack']['id'] - if result != False and result['score']: - try: - print("succ hack!", file=sys.stderr) - files = { - ('hack_input', open('uoj_judger/work/hack_input.txt', 'r')), - ('std_output', open('uoj_judger/work/std_output.txt', 'r')) - } - except Exception: - print_judge_client_status() - traceback.print_exc() - result = False - elif 'is_custom_test' in submission: - data['is_custom_test'] = True - data['id'] = submission['id'] - else: - data['id'] = submission['id'] - - if result == False: - result = { - 'score': 0, - 'error': 'Judgement Failed', - 'details': 'Unknown Error' - } - result['status'] = 'Judged' - data['result'] = json.dumps(result, ensure_ascii=False) - - while True: - try: - ret = uoj_interact(data, files) - print(ret) - except Exception: - print_judge_client_status() - traceback.print_exc() - else: - break - time.sleep(2) - - try: - submission = json.loads(ret) - except Exception as e: - submission = None - return False - else: - return True + data = {"judger_name": jconf["judger_name"], "password": jconf["judger_password"]} + ret = requests.post(uoj_url("/judge/sync-judge-client"), data=data).text + if ret != "ok": + raise Exception("failed to sync judge clients: %s" % ret) + + +def send_and_fetch(result=None, fetch_new=True): + global submission + + """send judgement result, and fetch new submission to judge""" + + data = {} + files = {} + + if not fetch_new: + data["fetch_new"] = False + + if result is not None: + data["submit"] = True + if "is_hack" in submission: + data["is_hack"] = True + data["id"] = submission["hack"]["id"] + if result != False and result["score"]: + try: + print("succ hack!", file=sys.stderr) + files = { + ("hack_input", open("uoj_judger/work/hack_input.txt", "r")), + ("std_output", open("uoj_judger/work/std_output.txt", "r")), + } + except Exception: + print_judge_client_status() + traceback.print_exc() + result = False + elif "is_custom_test" in submission: + data["is_custom_test"] = True + data["id"] = submission["id"] + else: + data["id"] = submission["id"] + + if result == False: + result = {"score": 0, "error": "Judgement Failed", "details": "Unknown Error"} + result["status"] = "Judged" + data["result"] = json.dumps(result, ensure_ascii=False) + + while True: + try: + ret = uoj_interact(data, files) + print(ret) + except Exception: + print_judge_client_status() + traceback.print_exc() + else: + break + time.sleep(2) + + try: + submission = json.loads(ret) + except Exception as e: + submission = None + return False + else: + return True + # judge client def judger_loop(): - ok = False - while True: - fetch_new = True - - if ok and not (taskQ.empty() and socket_server_thread.isAlive()): - fetch_new = False - - if not ok: - while True: - if not taskQ.empty(): - handle_task() - if not socket_server_thread.isAlive(): - raise Exception('socket server exited unexpectedly') - - if send_and_fetch(): - break - - print('['+time.asctime()+']', 'Nothing to judge...') - time.sleep(2) - - ok = True - print_judge_client_status() - print('judging', file=sys.stderr) - - try: - res = judge() - except Exception: - print_judge_client_status() - traceback.print_exc() - res = False - - ok = send_and_fetch(result=res,fetch_new=fetch_new) + ok = False + while True: + fetch_new = True + + if ok and not (taskQ.empty() and socket_server_thread.is_alive()): + fetch_new = False + + if not ok: + while True: + if not taskQ.empty(): + handle_task() + if not socket_server_thread.is_alive(): + raise Exception("socket server exited unexpectedly") + + if send_and_fetch(): + break + + print("[" + time.asctime() + "]", "Nothing to judge...") + time.sleep(2) + + ok = True + print_judge_client_status() + print("judging", file=sys.stderr) + + try: + res = judge() + except Exception: + print_judge_client_status() + traceback.print_exc() + res = False + + ok = send_and_fetch(result=res, fetch_new=fetch_new) + # main function def main(): - init() - - if len(sys.argv) == 1: - start_judger_server() - if len(sys.argv) == 2: - if sys.argv[1] == 'start': - pid = os.fork() - if pid == -1: - raise Exception('fork failed') - elif pid > 0: - return - else: - freopen(sys.stdout, open(os.devnull, 'wb')) - freopen(sys.stderr, open('log/judge.log', 'ab', buffering=0)) - start_judger_server() - elif sys.argv[1] == 'update': - try: - with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: - s.connect(('127.0.0.1', jconf['socket_port'])) - s.sendall(json.dumps({ - 'password': jconf['socket_password'], - 'cmd': 'update' - }).encode()) - return - except Exception: - traceback.print_exc() - raise Exception('update failed') - elif sys.argv[1] == 'stop': - try: - with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: - s.connect(('127.0.0.1', jconf['socket_port'])) - s.sendall(json.dumps({ - 'password': jconf['socket_password'], - 'cmd': 'stop' - }).encode()) - if s.recv(10).decode() != 'ok': - raise Exception('stop failed') - return - except Exception: - traceback.print_exc() - raise Exception('stop failed') - raise Exception('invalid argument') + init() + + if len(sys.argv) == 1: + start_judger_server() + if len(sys.argv) == 2: + if sys.argv[1] == "start": + pid = os.fork() + if pid == -1: + raise Exception("fork failed") + elif pid > 0: + return + else: + freopen(sys.stdout, open(os.devnull, "wb")) + freopen(sys.stderr, open("log/judge.log", "ab", buffering=0)) + start_judger_server() + elif sys.argv[1] == "update": + try: + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.connect(("127.0.0.1", jconf["socket_port"])) + s.sendall( + json.dumps({"password": jconf["socket_password"], "cmd": "update"}).encode() + ) + return + except Exception: + traceback.print_exc() + raise Exception("update failed") + elif sys.argv[1] == "stop": + try: + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.connect(("127.0.0.1", jconf["socket_port"])) + s.sendall( + json.dumps({"password": jconf["socket_password"], "cmd": "stop"}).encode() + ) + if s.recv(10).decode() != "ok": + raise Exception("stop failed") + return + except Exception: + traceback.print_exc() + raise Exception("stop failed") + raise Exception("invalid argument") + try: - main() + main() except Exception: - print_judge_client_status() - traceback.print_exc() - sys.exit(1) + print_judge_client_status() + traceback.print_exc() + sys.exit(1) diff --git a/judger/uoj_judger/.clang-format b/judger/uoj_judger/.clang-format new file mode 100644 index 000000000..6bacb76f4 --- /dev/null +++ b/judger/uoj_judger/.clang-format @@ -0,0 +1,46 @@ +# Generated based on project style and user requirements. +# Language: C++ +Language: Cpp +# Based on the LLVM style, but with significant customizations for strictness. +BasedOnStyle: Google + +# ------------------------------------------------------------------------------ +# 缩进与格式化 (Indentation and Formatting) +# ------------------------------------------------------------------------------ + +# 核心缩进规则,与 .editorconfig 保持一致 +UseTab: Never +IndentWidth: 4 +TabWidth: 4 +ContinuationIndentWidth: 4 + +# ------------------------------------------------------------------------------ +# 指针、引用和对齐 (Pointers, References, and Alignment) +# ------------------------------------------------------------------------------ + +# 根据代码 `const string &arg` 和 `const char *cmd` 的风格 +PointerAlignment: Right + +# ------------------------------------------------------------------------------ +# 换行与列宽 (Line Breaking and Column Limit) +# ------------------------------------------------------------------------------ + +# 列宽限制 +ColumnLimit: 100 +# 尽可能在赋值操作符 (=, += 等) 之前换行,而不是之后 +BreakBeforeBinaryOperators: NonAssignment +# 构造函数的初始化列表要么全在一行,要么每个成员一行 +BreakConstructorInitializers: AfterColon +# 允许在 C++11 的大括号列表 { ... } 中换行 +Cpp11BracedListStyle: true + +# ------------------------------------------------------------------------------ +# 其他增强可读性和严格性的选项 (Other Readability/Strictness Options) +# ------------------------------------------------------------------------------ + +# 将 public/private/protected 访问修饰符向左缩进 +AccessModifierOffset: -4 + +# 禁止短非空语句放在同一行 +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Empty diff --git a/judger/uoj_judger/.clang-format-ignore b/judger/uoj_judger/.clang-format-ignore new file mode 100644 index 000000000..611df8b05 --- /dev/null +++ b/judger/uoj_judger/.clang-format-ignore @@ -0,0 +1 @@ +include/testlib.h \ No newline at end of file diff --git a/judger/uoj_judger/Makefile b/judger/uoj_judger/Makefile index b69f50ff6..e53b5f96c 100644 --- a/judger/uoj_judger/Makefile +++ b/judger/uoj_judger/Makefile @@ -1,5 +1,5 @@ INCLUDE_PATH = include -CXXFLAGS = -I./include -O2 +CXXFLAGS = -I./include -O2 --std=c++17 -Wall -lstdc++fs EXE_CHECKER = \ builtin/checker/bcmp \ @@ -26,24 +26,35 @@ EXE = main_judger \ run/formatter \ run/run_program \ run/run_interaction \ - builtin/judger/judger + run/compile \ + builtin/judger/judger \ + $(EXE_CHECKER) -all: $(EXE) $(EXE_CHECKER) -runner: $(EXE) -checker: $(EXE_CHECKER) +OBJ = tests/catch_amalgamated.o tests/test.o + +all: $(EXE) % : %.cpp $(CXX) $(CXXFLAGS) $(EXTRA_CXXFLAGS) $< -o $@ -run/run_program: include/uoj_env.h run/run_program_conf.h -run/formatter : include/testlib.h -run/run_interaction: run/run_interaction.cpp include/uoj_env.h - $(CXX) $(CXXFLAGS) --std=c++11 -pthread $< -o $@ +run/formatter : include/testlib.h +run/compile : include/uoj_run.h + +run/run_program : run/run_program.cpp run/run_program_sandbox.h include/uoj_run.h + $(CXX) $(CXXFLAGS) $< -o $@ -lseccomp -pthread + +run/run_interaction : run/run_interaction.cpp include/uoj_run.h + $(CXX) $(CXXFLAGS) $< -o $@ -pthread + +builtin/judger/judger: include/*.h +main_judger: include/*.h + +tests/test.o: include/*.h -builtin/judger/judger: include -main_judger: include +tests/test: tests/catch_amalgamated.o tests/test.o + $(CXX) $(CXXFLAGS) $^ -o $@ -pthread $(EXE_CHECKER): include/testlib.h clean: - rm -f $(EXE) $(EXE_CHECKER) + rm -f $(EXE) $(OBJ) diff --git a/judger/uoj_judger/builtin/checker/acmp.cpp b/judger/uoj_judger/builtin/checker/acmp.cpp index 06184f02c..f8286ab41 100644 --- a/judger/uoj_judger/builtin/checker/acmp.cpp +++ b/judger/uoj_judger/builtin/checker/acmp.cpp @@ -1,19 +1,17 @@ -#include "testlib.h" -#include #include +#include "testlib.h" + const double EPS = 1.5E-6; -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare two doubles, maximal absolute error = %.10lf", EPS); registerTestlibCmd(argc, argv); - + double ja = ans.readDouble(); double pa = ouf.readDouble(); - if (fabs(ja - pa) > EPS + 1E-15) - quitf(_wa, "expected %.10lf, found %.10lf", ja, pa); - + if (fabs(ja - pa) > EPS + 1E-15) quitf(_wa, "expected %.10lf, found %.10lf", ja, pa); + quitf(_ok, "answer is %.10lf", ja); } diff --git a/judger/uoj_judger/builtin/checker/bcmp.cpp b/judger/uoj_judger/builtin/checker/bcmp.cpp index deba9d191..86f61b63a 100644 --- a/judger/uoj_judger/builtin/checker/bcmp.cpp +++ b/judger/uoj_judger/builtin/checker/bcmp.cpp @@ -1,55 +1,47 @@ #include -inline const char *englishEnding(int x) -{ - x %= 100; - if (x / 10 == 1) - return "th"; - if (x % 10 == 1) - return "st"; - if (x % 10 == 2) - return "nd"; - if (x % 10 == 3) - return "rd"; - return "th"; +inline const char *englishEnding(int x) { + x %= 100; + if (x / 10 == 1) return "th"; + if (x % 10 == 1) return "st"; + if (x % 10 == 2) return "nd"; + if (x % 10 == 3) return "rd"; + return "th"; } -int main(int argc, char * argv[]) -{ - if (argc != 4) - return 1; - FILE *fout = fopen(argv[2], "r"); - FILE *fans = fopen(argv[3], "r"); +int main(int argc, char *argv[]) { + if (argc != 4) return 1; + FILE *fout = fopen(argv[2], "r"); + FILE *fans = fopen(argv[3], "r"); - if (fout == NULL || fans == NULL) - return 1; + if (fout == NULL || fans == NULL) return 1; - int n = 0; - while (true) - { - n++; - int c, d; - c = fgetc(fout); - d = fgetc(fans); - if (c == EOF && d == EOF) - break; - if (c != d) - { - if (c == EOF) - fprintf(stderr, "wrong answer %d%s byte differ - expected EOF found '%c'\n", n, englishEnding(n), (char)d); - else if (d == EOF) - fprintf(stderr, "wrong answer %d%s byte differ - expected '%c' found EOF\n", n, englishEnding(n), (char)c); - else - fprintf(stderr, "wrong answer %d%s byte differ - expected '%c' found '%c'\n", n, englishEnding(n), (char)c, (char)d); - return 1; - } - } + int n = 0; + while (true) { + n++; + int c, d; + c = fgetc(fout); + d = fgetc(fans); + if (c == EOF && d == EOF) break; + if (c != d) { + if (c == EOF) + fprintf(stderr, "wrong answer %d%s byte differ - expected EOF found '%c'\n", n, + englishEnding(n), (char)d); + else if (d == EOF) + fprintf(stderr, "wrong answer %d%s byte differ - expected '%c' found EOF\n", n, + englishEnding(n), (char)c); + else + fprintf(stderr, "wrong answer %d%s byte differ - expected '%c' found '%c'\n", n, + englishEnding(n), (char)c, (char)d); + return 1; + } + } - if (n == 0) - fprintf(stderr, "ok empty file\n"); - else if (n == 1) - fprintf(stderr, "ok single byte\n"); - else - fprintf(stderr, "ok %d byte\n", n); - return 0; + if (n == 0) + fprintf(stderr, "ok empty file\n"); + else if (n == 1) + fprintf(stderr, "ok single byte\n"); + else + fprintf(stderr, "ok %d byte\n", n); + return 0; } diff --git a/judger/uoj_judger/builtin/checker/caseicmp.cpp b/judger/uoj_judger/builtin/checker/caseicmp.cpp index 099a9173e..0a592074e 100644 --- a/judger/uoj_judger/builtin/checker/caseicmp.cpp +++ b/judger/uoj_judger/builtin/checker/caseicmp.cpp @@ -8,43 +8,33 @@ * */ -#include "testlib.h" - -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include +#include #include -#include #include -#include -#include +#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include + +#include "testlib.h" using namespace std; #define forn(i, n) for (int i = 0; i < int(n); i++) -vector readStream(InStream& in, TResult pe) -{ +vector readStream(InStream& in, TResult pe) { vector result; - for (int testCase = 1; !in.seekEof(); testCase++) - { + for (int testCase = 1; !in.seekEof(); testCase++) { string caseStr = in.readToken(); if (caseStr != "Case") - quitf(pe, "Expected 'Case' but found '%s' [test case %d]", compress(caseStr).c_str(), testCase); + quitf(pe, "Expected 'Case' but found '%s' [test case %d]", compress(caseStr).c_str(), + testCase); string numExpStr; stringstream ss; @@ -53,7 +43,8 @@ vector readStream(InStream& in, TResult pe) numExpStr += ":"; string numStr = in.readToken(); if (numExpStr != numStr) - quitf(pe, "Expected '%s' but found '%s' [test case %d]", compress(numExpStr).c_str(), compress(numStr).c_str(), testCase); + quitf(pe, "Expected '%s' but found '%s' [test case %d]", compress(numExpStr).c_str(), + compress(numStr).c_str(), testCase); result.push_back(in.readLong()); } @@ -61,34 +52,27 @@ vector readStream(InStream& in, TResult pe) return result; } -int main(int argc, char* argv[]) -{ +int main(int argc, char* argv[]) { setName("Single int64 checker with testcase-support"); registerTestlibCmd(argc, argv); vector ja = readStream(ans, _fail); vector pa = readStream(ouf, _pe); - forn(i, min(ja.size(), pa.size())) - if (ja[i] != pa[i]) - quitf(_wa, "Expected %s found %s [test case %d]", vtos(ja[i]).c_str(), vtos(pa[i]).c_str(), i + 1); + forn(i, min(ja.size(), pa.size())) if (ja[i] != pa[i]) + quitf(_wa, "Expected %s found %s [test case %d]", vtos(ja[i]).c_str(), vtos(pa[i]).c_str(), + i + 1); if (ja.size() != pa.size()) quitf(_pe, "Expected %d test case(s) but found %d", (int)ja.size(), (int)pa.size()); string message = format("%d case(s):", (int)ja.size()); - if (ja.size() <= 5) - { - forn(i, ja.size()) - message += " " + vtos(ja[i]); - } - else - { - forn(i, 3) - message += " " + vtos(ja[i]); + if (ja.size() <= 5) { + forn(i, ja.size()) message += " " + vtos(ja[i]); + } else { + forn(i, 3) message += " " + vtos(ja[i]); message += " ..."; - forn(i, 2) - message += " " + vtos(ja[ja.size() - 2 + i]); + forn(i, 2) message += " " + vtos(ja[ja.size() - 2 + i]); } quitf(_ok, "%s", message.c_str()); diff --git a/judger/uoj_judger/builtin/checker/casencmp.cpp b/judger/uoj_judger/builtin/checker/casencmp.cpp index 58ea8e33e..d71f5a623 100644 --- a/judger/uoj_judger/builtin/checker/casencmp.cpp +++ b/judger/uoj_judger/builtin/checker/casencmp.cpp @@ -8,29 +8,19 @@ * */ -#include "testlib.h" - -#include -#include -#include -#include -#include -#include +#include +#include +#include #include +#include #include -#include #include -#include -#include +#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include + +#include "testlib.h" using namespace std; @@ -38,13 +28,12 @@ using namespace std; string token; -vector readStreamCase(InStream& in, TResult pe, int testCase, bool& prereadCase) -{ - if (!prereadCase) - { +vector readStreamCase(InStream& in, TResult pe, int testCase, bool& prereadCase) { + if (!prereadCase) { string caseStr = in.readToken(); if (caseStr != "Case") - quitf(pe, "Expected 'Case' but found '%s' [test case %d]", compress(caseStr).c_str(), testCase); + quitf(pe, "Expected 'Case' but found '%s' [test case %d]", compress(caseStr).c_str(), + testCase); } string numExpStr; @@ -54,14 +43,13 @@ vector readStreamCase(InStream& in, TResult pe, int testCase, bool& p numExpStr += ":"; string numStr = in.readToken(); if (numExpStr != numStr) - quitf(pe, "Expected '%s' but found '%s' [test case %d]", compress(numExpStr).c_str(), compress(numStr).c_str(), testCase); + quitf(pe, "Expected '%s' but found '%s' [test case %d]", compress(numExpStr).c_str(), + compress(numStr).c_str(), testCase); vector result; - while (!in.seekEof()) - { + while (!in.seekEof()) { in.readTokenTo(token); - if (token == "Case") - { + if (token == "Case") { prereadCase = true; break; } @@ -72,31 +60,22 @@ vector readStreamCase(InStream& in, TResult pe, int testCase, bool& p return result; } -string longLongsToString(const vector& a) -{ - if (a.empty()) - return "\"\" [size=0]"; - +string longLongsToString(const vector& a) { + if (a.empty()) return "\"\" [size=0]"; + string elems; - if (a.size() <= 5) - { - forn(i, a.size()) - elems += vtos(a[i]) + " "; - } - else - { - forn(i, 3) - elems += vtos(a[i]) + " "; + if (a.size() <= 5) { + forn(i, a.size()) elems += vtos(a[i]) + " "; + } else { + forn(i, 3) elems += vtos(a[i]) + " "; elems += "... "; - forn(i, 2) - elems += vtos(a[a.size() - 2 + i]) + " "; + forn(i, 2) elems += vtos(a[a.size() - 2 + i]) + " "; } return format("\"%s\" [size=%d]", trim(elems).c_str(), (int)a.size()); } -int main(int argc, char* argv[]) -{ +int main(int argc, char* argv[]) { setName("Many int64s checker with testcase-support"); registerTestlibCmd(argc, argv); @@ -105,18 +84,17 @@ int main(int argc, char* argv[]) bool ansPrereadCase = false; bool oufPrereadCase = false; - while (!ans.seekEof()) - { + while (!ans.seekEof()) { testCase++; vector ja = readStreamCase(ans, _fail, testCase, ansPrereadCase); vector pa = readStreamCase(ouf, _pe, testCase, oufPrereadCase); - if (ja != pa) - { + if (ja != pa) { string js = longLongsToString(ja); string ps = longLongsToString(pa); - quitf(_wa, "Sequences differ: jury has %s, but participant has %s [test case %d]", js.c_str(), ps.c_str(), testCase); + quitf(_wa, "Sequences differ: jury has %s, but participant has %s [test case %d]", + js.c_str(), ps.c_str(), testCase); } } diff --git a/judger/uoj_judger/builtin/checker/casewcmp.cpp b/judger/uoj_judger/builtin/checker/casewcmp.cpp index 4684ccabc..dd2517e05 100644 --- a/judger/uoj_judger/builtin/checker/casewcmp.cpp +++ b/judger/uoj_judger/builtin/checker/casewcmp.cpp @@ -8,29 +8,19 @@ * */ -#include "testlib.h" - -#include -#include -#include -#include -#include -#include +#include +#include +#include #include +#include #include -#include #include -#include -#include +#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include + +#include "testlib.h" using namespace std; @@ -38,13 +28,12 @@ using namespace std; string token; -vector readStreamCase(InStream& in, TResult pe, int testCase, bool& prereadCase) -{ - if (!prereadCase) - { +vector readStreamCase(InStream& in, TResult pe, int testCase, bool& prereadCase) { + if (!prereadCase) { string caseStr = in.readToken(); if (caseStr != "Case") - quitf(pe, "Expected 'Case' but found '%s' [test case %d]", compress(caseStr).c_str(), testCase); + quitf(pe, "Expected 'Case' but found '%s' [test case %d]", compress(caseStr).c_str(), + testCase); } string numExpStr; @@ -54,14 +43,13 @@ vector readStreamCase(InStream& in, TResult pe, int testCase, bool& prer numExpStr += ":"; string numStr = in.readToken(); if (numExpStr != numStr) - quitf(pe, "Expected '%s' but found '%s' [test case %d]", compress(numExpStr).c_str(), compress(numStr).c_str(), testCase); + quitf(pe, "Expected '%s' but found '%s' [test case %d]", compress(numExpStr).c_str(), + compress(numStr).c_str(), testCase); vector result; - while (!in.seekEof()) - { + while (!in.seekEof()) { in.readTokenTo(token); - if (token == "Case") - { + if (token == "Case") { prereadCase = true; break; } @@ -72,20 +60,16 @@ vector readStreamCase(InStream& in, TResult pe, int testCase, bool& prer return result; } -string stringsToString(const vector& a) -{ - if (a.empty()) - return "\"\" [size=0]"; - +string stringsToString(const vector& a) { + if (a.empty()) return "\"\" [size=0]"; + string elems; - forn(i, a.size()) - elems += a[i] + " "; + forn(i, a.size()) elems += a[i] + " "; return format("\"%s\" [size=%d]", compress(trim(elems)).c_str(), (int)a.size()); } -int main(int argc, char* argv[]) -{ +int main(int argc, char* argv[]) { setName("Tokens checker with testcase-support"); registerTestlibCmd(argc, argv); @@ -94,18 +78,17 @@ int main(int argc, char* argv[]) bool ansPrereadCase = false; bool oufPrereadCase = false; - while (!ans.seekEof()) - { + while (!ans.seekEof()) { testCase++; vector ja = readStreamCase(ans, _fail, testCase, ansPrereadCase); vector pa = readStreamCase(ouf, _pe, testCase, oufPrereadCase); - if (ja != pa) - { + if (ja != pa) { string js = stringsToString(ja); string ps = stringsToString(pa); - quitf(_wa, "Sequences differ: jury has %s, but participant has %s [test case %d]", js.c_str(), ps.c_str(), testCase); + quitf(_wa, "Sequences differ: jury has %s, but participant has %s [test case %d]", + js.c_str(), ps.c_str(), testCase); } } diff --git a/judger/uoj_judger/builtin/checker/dcmp.cpp b/judger/uoj_judger/builtin/checker/dcmp.cpp index c3b75b3e2..c1e20d941 100644 --- a/judger/uoj_judger/builtin/checker/dcmp.cpp +++ b/judger/uoj_judger/builtin/checker/dcmp.cpp @@ -1,19 +1,17 @@ -#include "testlib.h" -#include #include +#include "testlib.h" + const double EPS = 1E-6; -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare two doubles, maximal absolute or relative error = %.10lf", EPS); registerTestlibCmd(argc, argv); - + double ja = ans.readDouble(); double pa = ouf.readDouble(); - - if (!doubleCompare(ja, pa, EPS)) - quitf(_wa, "expected %.10lf, found %.10lf", ja, pa); - + + if (!doubleCompare(ja, pa, EPS)) quitf(_wa, "expected %.10lf, found %.10lf", ja, pa); + quitf(_ok, "answer is %.10lf", ja); } diff --git a/judger/uoj_judger/builtin/checker/fcmp.cpp b/judger/uoj_judger/builtin/checker/fcmp.cpp index 9bf2c3396..e5bad3715 100644 --- a/judger/uoj_judger/builtin/checker/fcmp.cpp +++ b/judger/uoj_judger/builtin/checker/fcmp.cpp @@ -1,24 +1,20 @@ -#include "testlib.h" #include -#include -#include + +#include "testlib.h" using namespace std; -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare files as sequence of lines"); registerTestlibCmd(argc, argv); std::string strAnswer; int n = 0; - while (!ans.eof()) - { + while (!ans.eof()) { std::string j = ans.readString(); - if (j == "" && ans.eof()) - break; + if (j == "" && ans.eof()) break; strAnswer = j; std::string p = ouf.readString(); @@ -26,11 +22,11 @@ int main(int argc, char * argv[]) n++; if (j != p) - quitf(_wa, "%d%s lines differ - expected: '%s', found: '%s'", n, englishEnding(n).c_str(), compress(j).c_str(), compress(p).c_str()); + quitf(_wa, "%d%s lines differ - expected: '%s', found: '%s'", n, + englishEnding(n).c_str(), compress(j).c_str(), compress(p).c_str()); } - - if (n == 1) - quitf(_ok, "single line: '%s'", compress(strAnswer).c_str()); - + + if (n == 1) quitf(_ok, "single line: '%s'", compress(strAnswer).c_str()); + quitf(_ok, "%d lines", n); } diff --git a/judger/uoj_judger/builtin/checker/hcmp.cpp b/judger/uoj_judger/builtin/checker/hcmp.cpp index c02a9b2f7..6fc9a29d6 100644 --- a/judger/uoj_judger/builtin/checker/hcmp.cpp +++ b/judger/uoj_judger/builtin/checker/hcmp.cpp @@ -1,35 +1,30 @@ -#include "testlib.h" - #include +#include "testlib.h" + using namespace std; pattern pnum("0|-?[1-9][0-9]*"); -bool isNumeric(const string& p) -{ +bool isNumeric(const string& p) { return pnum.matches(p); } -int main(int argc, char * argv[]) -{ +int main(int argc, char* argv[]) { setName("compare two signed huge integers"); registerTestlibCmd(argc, argv); - + string ja = ans.readWord(); string pa = ouf.readWord(); - if (!isNumeric(ja)) - quitf(_fail, "%s is not a valid integer", compress(ja).c_str()); + if (!isNumeric(ja)) quitf(_fail, "%s is not a valid integer", compress(ja).c_str()); + + if (!ans.seekEof()) quitf(_fail, "expected exactly one token in the answer file"); - if (!ans.seekEof()) - quitf(_fail, "expected exactly one token in the answer file"); - - if (!isNumeric(pa)) - quitf(_pe, "%s is not a valid integer", compress(pa).c_str()); + if (!isNumeric(pa)) quitf(_pe, "%s is not a valid integer", compress(pa).c_str()); if (ja != pa) quitf(_wa, "expected '%s', found '%s'", compress(ja).c_str(), compress(pa).c_str()); - + quitf(_ok, "answer is '%s'", compress(ja).c_str()); } diff --git a/judger/uoj_judger/builtin/checker/icmp.cpp b/judger/uoj_judger/builtin/checker/icmp.cpp index d587d2c42..cfee4ab77 100644 --- a/judger/uoj_judger/builtin/checker/icmp.cpp +++ b/judger/uoj_judger/builtin/checker/icmp.cpp @@ -1,16 +1,13 @@ #include "testlib.h" -#include -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare two signed int%d's", (int)(8 * sizeof(int))); registerTestlibCmd(argc, argv); - + int ja = ans.readInt(); int pa = ouf.readInt(); - - if (ja != pa) - quitf(_wa, "expected %d, found %d", ja, pa); - + + if (ja != pa) quitf(_wa, "expected %d, found %d", ja, pa); + quitf(_ok, "answer is %d", ja); } diff --git a/judger/uoj_judger/builtin/checker/lcmp.cpp b/judger/uoj_judger/builtin/checker/lcmp.cpp index f71fe6abe..f75b6698d 100644 --- a/judger/uoj_judger/builtin/checker/lcmp.cpp +++ b/judger/uoj_judger/builtin/checker/lcmp.cpp @@ -1,54 +1,49 @@ -#include "testlib.h" +#include #include #include -#include + +#include "testlib.h" using namespace std; -bool compareWords(string a, string b) -{ +bool compareWords(string a, string b) { vector va, vb; stringstream sa; - + sa << a; string cur; - while (sa >> cur) - va.push_back(cur); + while (sa >> cur) va.push_back(cur); stringstream sb; sb << b; - while (sb >> cur) - vb.push_back(cur); + while (sb >> cur) vb.push_back(cur); return (va == vb); } -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare files as sequence of tokens in lines"); registerTestlibCmd(argc, argv); std::string strAnswer; int n = 0; - while (!ans.eof()) - { + while (!ans.eof()) { std::string j = ans.readString(); - if (j == "" && ans.eof()) - break; - + if (j == "" && ans.eof()) break; + std::string p = ouf.readString(); strAnswer = p; n++; if (!compareWords(j, p)) - quitf(_wa, "%d%s lines differ - expected: '%s', found: '%s'", n, englishEnding(n).c_str(), compress(j).c_str(), compress(p).c_str()); + quitf(_wa, "%d%s lines differ - expected: '%s', found: '%s'", n, + englishEnding(n).c_str(), compress(j).c_str(), compress(p).c_str()); } - - if (n == 1) - quitf(_ok, "single line: '%s'", compress(strAnswer).c_str()); - + + if (n == 1) quitf(_ok, "single line: '%s'", compress(strAnswer).c_str()); + quitf(_ok, "%d lines", n); } diff --git a/judger/uoj_judger/builtin/checker/ncmp.cpp b/judger/uoj_judger/builtin/checker/ncmp.cpp index 9c95daeb3..898c7e05c 100644 --- a/judger/uoj_judger/builtin/checker/ncmp.cpp +++ b/judger/uoj_judger/builtin/checker/ncmp.cpp @@ -1,10 +1,8 @@ #include "testlib.h" -#include using namespace std; -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare ordered sequences of signed int%d numbers", (int)(8 * sizeof(long long))); registerTestlibCmd(argc, argv); @@ -12,44 +10,41 @@ int main(int argc, char * argv[]) int n = 0; string firstElems; - while (!ans.seekEof() && !ouf.seekEof()) - { + while (!ans.seekEof() && !ouf.seekEof()) { n++; long long j = ans.readLong(); long long p = ouf.readLong(); if (j != p) - quitf(_wa, "%d%s numbers differ - expected: '%s', found: '%s'", n, englishEnding(n).c_str(), vtos(j).c_str(), vtos(p).c_str()); - else - if (n <= 5) - { - if (firstElems.length() > 0) - firstElems += " "; - firstElems += vtos(j); - } + quitf(_wa, "%d%s numbers differ - expected: '%s', found: '%s'", n, + englishEnding(n).c_str(), vtos(j).c_str(), vtos(p).c_str()); + else if (n <= 5) { + if (firstElems.length() > 0) firstElems += " "; + firstElems += vtos(j); + } } int extraInAnsCount = 0; - while (!ans.seekEof()) - { + while (!ans.seekEof()) { ans.readLong(); extraInAnsCount++; } - + int extraInOufCount = 0; - while (!ouf.seekEof()) - { + while (!ouf.seekEof()) { ouf.readLong(); extraInOufCount++; } if (extraInAnsCount > 0) - quitf(_wa, "Answer contains longer sequence [length = %d], but output contains %d elements", n + extraInAnsCount, n); - + quitf(_wa, "Answer contains longer sequence [length = %d], but output contains %d elements", + n + extraInAnsCount, n); + if (extraInOufCount > 0) - quitf(_wa, "Output contains longer sequence [length = %d], but answer contains %d elements", n + extraInOufCount, n); - + quitf(_wa, "Output contains longer sequence [length = %d], but answer contains %d elements", + n + extraInOufCount, n); + if (n <= 5) quitf(_ok, "%d number(s): \"%s\"", n, compress(firstElems).c_str()); else diff --git a/judger/uoj_judger/builtin/checker/nyesno.cpp b/judger/uoj_judger/builtin/checker/nyesno.cpp new file mode 120000 index 000000000..7943c8200 --- /dev/null +++ b/judger/uoj_judger/builtin/checker/nyesno.cpp @@ -0,0 +1 @@ +yesno.cpp \ No newline at end of file diff --git a/judger/uoj_judger/builtin/checker/rcmp.cpp b/judger/uoj_judger/builtin/checker/rcmp.cpp index 06184f02c..b5c146a6e 100644 --- a/judger/uoj_judger/builtin/checker/rcmp.cpp +++ b/judger/uoj_judger/builtin/checker/rcmp.cpp @@ -1,19 +1,17 @@ +#include + #include "testlib.h" -#include -#include const double EPS = 1.5E-6; -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare two doubles, maximal absolute error = %.10lf", EPS); registerTestlibCmd(argc, argv); - + double ja = ans.readDouble(); double pa = ouf.readDouble(); - if (fabs(ja - pa) > EPS + 1E-15) - quitf(_wa, "expected %.10lf, found %.10lf", ja, pa); - + if (fabs(ja - pa) > EPS + 1E-15) quitf(_wa, "expected %.10lf, found %.10lf", ja, pa); + quitf(_ok, "answer is %.10lf", ja); } diff --git a/judger/uoj_judger/builtin/checker/rcmp4.cpp b/judger/uoj_judger/builtin/checker/rcmp4.cpp index 8e9285549..78b2b15cc 100644 --- a/judger/uoj_judger/builtin/checker/rcmp4.cpp +++ b/judger/uoj_judger/builtin/checker/rcmp4.cpp @@ -1,32 +1,30 @@ -#include "testlib.h" #include +#include "testlib.h" + using namespace std; const double EPS = 1E-4; -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare two sequences of doubles, max absolute or relative error = %.5lf", EPS); registerTestlibCmd(argc, argv); int n = 0; double j, p; - while (!ans.seekEof()) - { + while (!ans.seekEof()) { n++; j = ans.readDouble(); p = ouf.readDouble(); - if (!doubleCompare(j, p, EPS)) - { + if (!doubleCompare(j, p, EPS)) { quitf(_wa, "%d%s numbers differ - expected: '%.5lf', found: '%.5lf', error = '%.5lf'", - n, englishEnding(n).c_str(), j, p, doubleDelta(j, p)); + n, englishEnding(n).c_str(), j, p, doubleDelta(j, p)); } } if (n == 1) quitf(_ok, "found '%.5lf', expected '%.5lf', error '%.5lf'", p, j, doubleDelta(j, p)); - + quitf(_ok, "%d numbers", n); } diff --git a/judger/uoj_judger/builtin/checker/rcmp6.cpp b/judger/uoj_judger/builtin/checker/rcmp6.cpp index 2d102fb86..ba02350c5 100644 --- a/judger/uoj_judger/builtin/checker/rcmp6.cpp +++ b/judger/uoj_judger/builtin/checker/rcmp6.cpp @@ -1,27 +1,25 @@ -#include "testlib.h" #include +#include "testlib.h" + using namespace std; const double EPS = 1E-6; -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare two sequences of doubles, max absolute or relative error = %.7lf", EPS); registerTestlibCmd(argc, argv); int n = 0; double j, p; - while (!ans.seekEof()) - { + while (!ans.seekEof()) { n++; j = ans.readDouble(); p = ouf.readDouble(); - if (!doubleCompare(j, p, EPS)) - { + if (!doubleCompare(j, p, EPS)) { quitf(_wa, "%d%s numbers differ - expected: '%.7lf', found: '%.7lf', error = '%.7lf'", - n, englishEnding(n).c_str(), j, p, doubleDelta(j, p)); + n, englishEnding(n).c_str(), j, p, doubleDelta(j, p)); } } diff --git a/judger/uoj_judger/builtin/checker/rcmp9.cpp b/judger/uoj_judger/builtin/checker/rcmp9.cpp index 13f475024..0d3812ff5 100644 --- a/judger/uoj_judger/builtin/checker/rcmp9.cpp +++ b/judger/uoj_judger/builtin/checker/rcmp9.cpp @@ -1,27 +1,25 @@ -#include "testlib.h" #include +#include "testlib.h" + using namespace std; const double EPS = 1E-9; -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare two sequences of doubles, max absolute or relative error = %.10lf", EPS); registerTestlibCmd(argc, argv); int n = 0; double j, p; - while (!ans.seekEof()) - { + while (!ans.seekEof()) { n++; j = ans.readDouble(); p = ouf.readDouble(); - if (!doubleCompare(j, p, EPS)) - { + if (!doubleCompare(j, p, EPS)) { quitf(_wa, "%d%s numbers differ - expected: '%.7lf', found: '%.7lf', error = '%.7lf'", - n, englishEnding(n).c_str(), j, p, doubleDelta(j, p)); + n, englishEnding(n).c_str(), j, p, doubleDelta(j, p)); } } diff --git a/judger/uoj_judger/builtin/checker/rncmp.cpp b/judger/uoj_judger/builtin/checker/rncmp.cpp index 61fdfbb53..863c4b9c8 100644 --- a/judger/uoj_judger/builtin/checker/rncmp.cpp +++ b/judger/uoj_judger/builtin/checker/rncmp.cpp @@ -1,23 +1,23 @@ -#include "testlib.h" #include +#include "testlib.h" + using namespace std; const double EPS = 1.5E-5; -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare two sequences of doubles, maximal absolute error = %.10lf", EPS); registerTestlibCmd(argc, argv); int n = 0; - while (!ans.seekEof()) - { + while (!ans.seekEof()) { n++; double j = ans.readDouble(); double p = ouf.readDouble(); if (fabs(j - p) > EPS + 1E-15) - quitf(_wa, "%d%s numbers differ - expected: '%.10lf', found: '%.10lf'", n, englishEnding(n).c_str(), j, p); + quitf(_wa, "%d%s numbers differ - expected: '%.10lf', found: '%.10lf'", n, + englishEnding(n).c_str(), j, p); } quitf(_ok, "%d numbers", n); diff --git a/judger/uoj_judger/builtin/checker/uncmp.cpp b/judger/uoj_judger/builtin/checker/uncmp.cpp index ed79a130d..e48ed84ee 100644 --- a/judger/uoj_judger/builtin/checker/uncmp.cpp +++ b/judger/uoj_judger/builtin/checker/uncmp.cpp @@ -1,21 +1,19 @@ -#include "testlib.h" #include +#include "testlib.h" + using namespace std; -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare unordered sequences of signed int%d numbers", (int)(8 * sizeof(long long))); registerTestlibCmd(argc, argv); vector ja, pa; - while (!ans.seekEof()) - ja.push_back(ans.readLong()); + while (!ans.seekEof()) ja.push_back(ans.readLong()); - while (!ouf.seekEof()) - pa.push_back(ouf.readLong()); + while (!ouf.seekEof()) pa.push_back(ouf.readLong()); if (ja.size() != pa.size()) quitf(_wa, "Expected %d elements, but %d found", (int)ja.size(), (int)pa.size()); @@ -24,10 +22,11 @@ int main(int argc, char * argv[]) sort(pa.begin(), pa.end()); if (ja != pa) - quitf(_wa, "Expected sequence and output are different (as unordered sequences) [size=%d]", (int)ja.size()); + quitf(_wa, "Expected sequence and output are different (as unordered sequences) [size=%d]", + (int)ja.size()); string message; - + if (ja.size() != 1) if (ja.empty()) message = "empty sequence"; @@ -35,18 +34,14 @@ int main(int argc, char * argv[]) message = vtos(ja.size()) + " numbers (in increasing order):"; else message = vtos(ja.size()) + " number:"; - + if (ja.size() <= 5) - for (int i = 0; i < min(int(ja.size()), 5); i++) - message += " " + vtos(ja[i]); - else - { - for (int i = 0; i < 2; i++) - message += " " + vtos(ja[i]); + for (int i = 0; i < min(int(ja.size()), 5); i++) message += " " + vtos(ja[i]); + else { + for (int i = 0; i < 2; i++) message += " " + vtos(ja[i]); message += " ..."; - for (int i = 0; i < 2; i++) - message += " " + vtos(ja[ja.size() - 2 + i]); + for (int i = 0; i < 2; i++) message += " " + vtos(ja[ja.size() - 2 + i]); } - + quitf(_ok, "%s", message.c_str()); } diff --git a/judger/uoj_judger/builtin/checker/wcmp.cpp b/judger/uoj_judger/builtin/checker/wcmp.cpp index fe4ae5e44..032948e36 100644 --- a/judger/uoj_judger/builtin/checker/wcmp.cpp +++ b/judger/uoj_judger/builtin/checker/wcmp.cpp @@ -2,34 +2,30 @@ using namespace std; -int main(int argc, char * argv[]) -{ +int main(int argc, char *argv[]) { setName("compare sequences of tokens"); registerTestlibCmd(argc, argv); int n = 0; string j, p; - while (!ans.seekEof() && !ouf.seekEof()) - { + while (!ans.seekEof() && !ouf.seekEof()) { n++; ans.readWordTo(j); ouf.readWordTo(p); - + if (j != p) - quitf(_wa, "%d%s words differ - expected: '%s', found: '%s'", n, englishEnding(n).c_str(), compress(j).c_str(), compress(p).c_str()); + quitf(_wa, "%d%s words differ - expected: '%s', found: '%s'", n, + englishEnding(n).c_str(), compress(j).c_str(), compress(p).c_str()); } - if (ans.seekEof() && ouf.seekEof()) - { + if (ans.seekEof() && ouf.seekEof()) { if (n == 1) quitf(_ok, "\"%s\"", compress(j).c_str()); else quitf(_ok, "%d tokens", n); - } - else - { + } else { if (ans.seekEof()) quitf(_wa, "Participant output contains extra tokens"); else diff --git a/judger/uoj_judger/builtin/checker/yesno.cpp b/judger/uoj_judger/builtin/checker/yesno.cpp index ea605c8d6..38ab1f5f9 100644 --- a/judger/uoj_judger/builtin/checker/yesno.cpp +++ b/judger/uoj_judger/builtin/checker/yesno.cpp @@ -1,27 +1,56 @@ #include "testlib.h" -#include using namespace std; const string YES = "YES"; const string NO = "NO"; -int main(int argc, char * argv[]) -{ - setName("YES or NO (case insensetive)"); +int main(int argc, char* argv[]) { + setName("compare ordered sequences of YES or NO (case insensetive)"); + registerTestlibCmd(argc, argv); - std::string ja = upperCase(ans.readWord()); - std::string pa = upperCase(ouf.readWord()); + int n = 0; + + while (!ans.seekEof() && !ouf.seekEof()) { + n++; + std::string ja = upperCase(ans.readWord()); + std::string pa = upperCase(ouf.readWord()); + + if (ja != YES && ja != NO) + quitf(_fail, "%d%s differ - %s or %s expected, but %s found", n, + englishEnding(n).c_str(), YES.c_str(), NO.c_str(), compress(ja).c_str()); + + if (pa != YES && pa != NO) + quitf(_pe, "%d%s differ - %s or %s expected, but %s found", n, englishEnding(n).c_str(), + YES.c_str(), NO.c_str(), compress(pa).c_str()); + + if (ja != pa) + quitf(_wa, "%d%s differ - expected: '%s', found: '%s'", n, englishEnding(n).c_str(), + vtos(ja).c_str(), vtos(pa).c_str()); + } + + int extraInAnsCount = 0; + + while (!ans.seekEof()) { + ans.readToken(); + extraInAnsCount++; + } + + int extraInOufCount = 0; - if (ja != YES && ja != NO) - quitf(_fail, "%s or %s expected in answer, but %s found", YES.c_str(), NO.c_str(), compress(ja).c_str()); + while (!ouf.seekEof()) { + ouf.readToken(); + extraInOufCount++; + } - if (pa != YES && pa != NO) - quitf(_pe, "%s or %s expected, but %s found", YES.c_str(), NO.c_str(), compress(pa).c_str()); + if (extraInAnsCount > 0) + quitf(_wa, "Answer contains longer sequence [length = %d], but output contains %d elements", + n + extraInAnsCount, n); - if (ja != pa) - quitf(_wa, "expected %s, found %s", compress(ja).c_str(), compress(pa).c_str()); + if (extraInOufCount > 0) + quitf(_wa, "Output contains longer sequence [length = %d], but answer contains %d elements", + n + extraInOufCount, n); - quitf(_ok, "answer is %s", ja.c_str()); + quitf(_ok, "%d token(s)", n); } diff --git a/judger/uoj_judger/builtin/judger/judger.cpp b/judger/uoj_judger/builtin/judger/judger.cpp index 118e15b4d..6d8ba50a3 100644 --- a/judger/uoj_judger/builtin/judger/judger.cpp +++ b/judger/uoj_judger/builtin/judger/judger.cpp @@ -1,261 +1,132 @@ -#include "uoj_judger.h" +#include -struct SubtaskInfo { - bool passed; - int score; +#include "uoj_judger.h" - SubtaskInfo() { - } - SubtaskInfo(const bool &_p, const int &_s) - : passed(_p), score(_s){} -}; +PointInfo submit_answer_test_point_with_auto_generation(int i, + TestPointConfig tpc = TestPointConfig()) { + static set compiled; + static set generated; + + int output_file_id = conf_int("output_file_id", i, i); + tpc.submit_answer = true; + tpc.output_file_name = + work_path + "/" + conf_output_file_name(conf_int("output_file_id", i, i)); + + string gen_name = conf_str("output_gen", output_file_id, ""); + if (gen_name.empty()) { + generated.insert(output_file_id); + } + if (generated.count(output_file_id)) { + return test_point("", i, tpc); + } + + tpc.limit = conf_run_limit("gen", output_file_id, RL_GENERATOR_DEFAULT); + generated.insert(output_file_id); + + if (!compiled.count(gen_name)) { + report_judge_status_f("Compiling %s", gen_name.c_str()); + if (auto c_ret = compile_submission_program(gen_name); !c_ret.succeeded) { + end_judge_compile_error(c_ret); + } + compiled.insert(gen_name); + } + + tpc.submit_answer = false; + return test_point(gen_name, i, tpc); +} void ordinary_test() { - int n = conf_int("n_tests", 10); - int m = conf_int("n_ex_tests", 0); - int nT = conf_int("n_subtasks", 0); - - if (!conf_is("submit_answer", "on")) { - report_judge_status_f("Compiling"); - RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer"); - if (!c_ret.succeeded) { - end_judge_compile_error(c_ret); - } - } - - bool passed = true; - if (nT == 0) { - for (int i = 1; i <= n; i++) { - report_judge_status_f("Judging Test #%d", i); - PointInfo po = test_point("answer", i); - if (po.scr != 100) { - passed = false; - } - po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n)); - add_point_info(po); - } - } else if (nT == 1 && conf_str("subtask_type", 1, "packed") == "packed") { - for (int i = 1; i <= n; i++) { - report_judge_status_f("Judging Test #%d", i); - PointInfo po = test_point("answer", i); - if (po.scr != 100) { - passed = false; - po.scr = i == 1 ? 0 : -100; - add_point_info(po); - break; - } else { - po.scr = i == 1 ? 100 : 0; - add_point_info(po); - } - } - } else { - map subtasks; - map minScore; - for (int t = 1; t <= nT; t++) { - string subtaskType = conf_str("subtask_type", t, "packed"); - int startI = conf_int("subtask_end", t - 1, 0) + 1; - int endI = conf_int("subtask_end", t, 0); - - vector points; - minScore[t] = 100; - - vector dependences; - if (conf_str("subtask_dependence", t, "none") == "many") { - string cur = "subtask_dependence_" + vtos(t); - int p = 1; - while (conf_int(cur, p, 0) != 0) { - dependences.push_back(conf_int(cur, p, 0)); - p++; - } - } else if (conf_int("subtask_dependence", t, 0) != 0) { - dependences.push_back(conf_int("subtask_dependence", t, 0)); - } - bool skipped = false; - for (vector::iterator it = dependences.begin(); it != dependences.end(); it++) { - if (subtaskType == "packed") { - if (!subtasks[*it].passed) { - skipped = true; - break; - } - } else if (subtaskType == "min") { - minScore[t] = min(minScore[t], minScore[*it]); - } - } - if (skipped) { - add_subtask_info(t, 0, "Skipped", points); - continue; - } - - int tfull = conf_int("subtask_score", t, 100 / nT); - int tscore = scale_score(minScore[t], tfull); - string info = "Accepted"; - for (int i = startI; i <= endI; i++) { - report_judge_status_f("Judging Test #%d of Subtask #%d", i, t); - PointInfo po = test_point("answer", i); - if (subtaskType == "packed") { - if (po.scr != 100) { - passed = false; - po.scr = i == startI ? 0 : -tfull; - tscore = 0; - points.push_back(po); - info = po.info; - break; - } else { - po.scr = i == startI ? tfull : 0; - tscore = tfull; - points.push_back(po); - } - } else if (subtaskType == "min") { - minScore[t] = min(minScore[t], po.scr); - if (po.scr != 100) { - passed = false; - } - po.scr = scale_score(po.scr, tfull); - if (po.scr <= tscore) { - tscore = po.scr; - points.push_back(po); - info = po.info; - } else { - points.push_back(po); - } - } - } - - subtasks[t] = SubtaskInfo(info == "Accepted", tscore); - - add_subtask_info(t, tscore, info, points); - } - } - if (conf_is("submit_answer", "on") || !passed) { - end_judge_ok(); - } - - tot_score = 100; - for (int i = 1; i <= m; i++) { - report_judge_status_f("Judging Extra Test #%d", i); - PointInfo po = test_point("answer", -i); - if (po.scr != 100) { - po.num = -1; - po.info = "Extra Test Failed : " + po.info + " on " + vtos(i); - po.scr = -3; - add_point_info(po); - end_judge_ok(); - } - } - if (m != 0) { - PointInfo po(-1, 0, -1, -1, "Extra Test Passed", "", "", ""); - add_point_info(po); - } - end_judge_ok(); + if (conf_is("submit_answer", "on")) { + PrimaryDataTestConfig pdtc; + pdtc.disable_ex_tests = true; + primary_data_test([](int i) { return submit_answer_test_point_with_auto_generation(i); }, + pdtc); + } else { + report_judge_status_f("Compiling"); + if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) { + end_judge_compile_error(c_ret); + } + primary_data_test([](int i) { return test_point("answer", i); }); + } + end_judge_ok(); } void hack_test() { - if (conf_is("submit_answer", "on")) { - end_judge_judgement_failed("Hack is not supported in this problem."); - } else { - RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer"); - if (!c_ret.succeeded) { - end_judge_compile_error(c_ret); - } - TestPointConfig tpc; - tpc.input_file_name = work_path + "/hack_input.txt"; - tpc.output_file_name = work_path + "/pro_output.txt"; - tpc.answer_file_name = work_path + "/std_output.txt"; - - PointInfo po = test_hack_point("answer", tpc); - add_point_info(po); - end_judge_ok(); - } + if (conf_is("submit_answer", "on")) { + end_judge_judgement_failed("Hack is not supported in this problem."); + } else { + if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) { + end_judge_compile_error(c_ret); + } + TestPointConfig tpc; + tpc.input_file_name = work_path + "/hack_input.txt"; + tpc.output_file_name = work_path + "/pro_output.txt"; + tpc.answer_file_name = work_path + "/std_output.txt"; + + PointInfo po = test_hack_point("answer", tpc); + add_point_info(po); + end_judge_ok(); + } } void sample_test() { - if (conf_is("submit_answer", "on")) { - int n = conf_int("n_tests", 10); - for (int i = 1; i <= n; i++) { - report_judge_status_f("Judging Test #%d", i); - if (conf_is("check_existence_only_in_sample_test", "on")) { - TestPointConfig tpc = TestPointConfig(); - tpc.auto_complete(i); - - string usrout = file_preview(tpc.output_file_name); - if (usrout == "") { - add_point_info(PointInfo(i, 0, -1, -1, - "default", - file_preview(tpc.input_file_name), usrout, - "wrong answer empty file\n")); - } else { - PointInfo po = PointInfo(i, 100, -1, -1, - "default", - file_preview(tpc.input_file_name), usrout, - "ok nonempty file\n"); - po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n)); - add_point_info(po); - } - } else { - PointInfo po = test_point("answer", i); - if (po.scr != 0) { - po.info = "Accepted"; - po.scr = 100; - } - po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n)); - po.res = "no comment"; - add_point_info(po); - } - } - end_judge_ok(); - } else { - report_judge_status_f("Compiling"); - RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer"); - if (!c_ret.succeeded) { - end_judge_compile_error(c_ret); - } - - int n = conf_int("n_sample_tests", 0); - bool passed = true; - for (int i = 1; i <= n; i++) { - report_judge_status_f("Judging Sample Test #%d", i); - PointInfo po = test_point("answer", -i); - po.num = i; - if (po.scr != 100) { - passed = false; - } - po.scr = scale_score(po.scr, 100 / n); - add_point_info(po); - } - if (passed) { - tot_score = 100; - } - end_judge_ok(); - } + if (conf_is("submit_answer", "on")) { + if (conf_is("check_existence_only_in_sample_test", "on")) { + main_data_test([](int i) { + TestPointConfig tpc = TestPointConfig(); + tpc.checker = "nonempty"; + return submit_answer_test_point_with_auto_generation(i, tpc); + }); + } else { + main_data_test([](int i) { + PointInfo po = submit_answer_test_point_with_auto_generation(i); + if (po.scr != 0) { + po.info = "Accepted"; + po.scr = 100; + } + po.res = "no comment"; + return po; + }); + } + end_judge_ok(); + } else { + report_judge_status_f("Compiling"); + if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) { + end_judge_compile_error(c_ret); + } + + sample_data_test([](int i) { return test_point("answer", i); }); + end_judge_ok(); + } } void custom_test() { - if (conf_is("submit_answer", "on")) { - end_judge_judgement_failed("Custom test is not supported in this problem."); - } else { - report_judge_status_f("Compiling"); - RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer"); - if (!c_ret.succeeded) { - end_judge_compile_error(c_ret); - } - - report_judge_status_f("Judging"); - add_custom_test_info(ordinary_custom_test("answer")); - - end_judge_ok(); - } + report_judge_status_f("Compiling"); + if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) { + end_judge_compile_error(c_ret); + } + + CustomTestConfig ctc; + if (conf_is("submit_answer", "on")) { + ctc.base_limit = conf_run_limit("gen", 0, RL_GENERATOR_DEFAULT); + } + + report_judge_status_f("Judging"); + add_custom_test_info(ordinary_custom_test("answer", ctc)); + + end_judge_ok(); } int main(int argc, char **argv) { - judger_init(argc, argv); - - if (conf_is("test_new_hack_only", "on")) { - hack_test(); - } else if (conf_is("test_sample_only", "on")) { - sample_test(); - } else if (conf_is("custom_test", "on")) { - custom_test(); - } else { - ordinary_test(); - } + judger_init(argc, argv); + + if (conf_is("test_new_hack_only", "on")) { + hack_test(); + } else if (conf_is("test_sample_only", "on")) { + sample_test(); + } else if (conf_is("custom_test", "on")) { + custom_test(); + } else { + ordinary_test(); + } } diff --git a/judger/uoj_judger/compile_flags.txt b/judger/uoj_judger/compile_flags.txt new file mode 100644 index 000000000..ecd9ef3cd --- /dev/null +++ b/judger/uoj_judger/compile_flags.txt @@ -0,0 +1,3 @@ +-Iinclude +-std=c++17 +-xc++ \ No newline at end of file diff --git a/judger/uoj_judger/include/testlib.h b/judger/uoj_judger/include/testlib.h index 26117e700..fe107b861 100644 --- a/judger/uoj_judger/include/testlib.h +++ b/judger/uoj_judger/include/testlib.h @@ -1,39 +1,39 @@ -/* - * It is strictly recommended to include "testlib.h" before any other include +/* + * It is strictly recommended to include "testlib.h" before any other include * in your code. In this case testlib overrides compiler specific "random()". * - * If you can't compile your code and compiler outputs something about - * ambiguous call of "random_shuffle", "rand" or "srand" it means that + * If you can't compile your code and compiler outputs something about + * ambiguous call of "random_shuffle", "rand" or "srand" it means that * you shouldn't use them. Use "shuffle", and "rnd.next()" instead of them - * because these calls produce stable result for any C++ compiler. Read + * because these calls produce stable result for any C++ compiler. Read * sample generator sources for clarification. * * Please read the documentation for class "random_t" and use "rnd" instance in - * generators. Probably, these sample calls will be usefull for you: - * rnd.next(); rnd.next(100); rnd.next(1, 2); + * generators. Probably, these sample calls will be useful for you: + * rnd.next(); rnd.next(100); rnd.next(1, 2); * rnd.next(3.14); rnd.next("[a-z]{1,100}"). * * Also read about wnext() to generate off-center random distribution. * - * See http://code.google.com/p/testlib/ to get latest version or bug tracker. + * See https://github.com/MikeMirzayanov/testlib/ to get latest version or bug tracker. */ #ifndef _TESTLIB_H_ #define _TESTLIB_H_ /* - * Copyright (c) 2005-2013 + * Copyright (c) 2005-2024 */ -#define VERSION "0.9.5" +#define VERSION "0.9.44" -/* +/* * Mike Mirzayanov * * This material is provided "as is", with absolutely no warranty expressed * or implied. Any use is at your own risk. * - * Permission to use or copy this software for any purpose is hereby granted + * Permission to use or copy this software for any purpose is hereby granted * without fee, provided the above notices are retained on all copies. * Permission to modify the code and to distribute modified code is granted, * provided the above notices are retained, and a notice that the code was @@ -47,76 +47,120 @@ * check.exe [ [-appes]], * If result file is specified it will contain results. * - * Validator, using testlib running format: + * Validator, using testlib running format: * validator.exe < input.txt, * It will return non-zero exit code and writes message to standard output. * - * Generator, using testlib running format: + * Generator, using testlib running format: * gen.exe [parameter-1] [parameter-2] [... paramerter-n] * You can write generated test(s) into standard output or into the file(s). * - * Interactor, using testlib running format: + * Interactor, using testlib running format: * interactor.exe [ [ [-appes]]], * Reads test from inf (mapped to args[1]), writes result to tout (mapped to argv[2], * can be judged by checker later), reads program output from ouf (mapped to stdin), * writes output to program via stdout (use cout, printf, etc). */ -const char* latestFeatures[] = { - "Removed disable buffers for interactive problems, because it works unexpectedly in wine", - "InStream over string: constructor of InStream from base InStream to inherit policies and std::string", - "Added expectedButFound quit function, examples: expectedButFound(_wa, 10, 20), expectedButFound(_fail, ja, pa, \"[n=%d,m=%d]\", n, m)", - "Fixed incorrect interval parsing in patterns", - "Use registerGen(argc, argv, 1) to develop new generator, use registerGen(argc, argv, 0) to compile old generators (originally created for testlib under 0.8.7)", - "Introduced disableFinalizeGuard() to switch off finalization checkings", - "Use join() functions to format a range of items as a single string (separated by spaces or other separators)", - "Use -DENABLE_UNEXPECTED_EOF to enable special exit code (by default, 8) in case of unexpected eof. It is good idea to use it in interactors", - "Use -DUSE_RND_AS_BEFORE_087 to compile in compatibility mode with random behavior of versions before 0.8.7", - "Fixed bug with nan in stringToDouble", - "Fixed issue around overloads for size_t on x64", - "Added attribute 'points' to the XML output in case of result=_points", - "Exit codes can be customized via macros, e.g. -DPE_EXIT_CODE=14", - "Introduced InStream function readWordTo/readTokenTo/readStringTo/readLineTo for faster reading", - "Introduced global functions: format(), englishEnding(), upperCase(), lowerCase(), compress()", - "Manual buffer in InStreams, some IO speed improvements", - "Introduced quitif(bool, const char* pattern, ...) which delegates to quitf() in case of first argument is true", - "Introduced guard against missed quitf() in checker or readEof() in validators", - "Supported readStrictReal/readStrictDouble - to use in validators to check strictly float numbers", - "Supported registerInteraction(argc, argv)", - "Print checker message to the stderr instead of stdout", - "Supported TResult _points to output calculated score, use quitp(...) functions", - "Fixed to be compilable on Mac", - "PC_BASE_EXIT_CODE=50 in case of defined TESTSYS", - "Fixed issues 19-21, added __attribute__ format printf", - "Some bug fixes", - "ouf.readInt(1, 100) and similar calls return WA", - "Modified random_t to avoid integer overflow", - "Truncated checker output [patch by Stepan Gatilov]", - "Renamed class random -> class random_t", - "Supported name parameter for read-and-validation methods, like readInt(1, 2, \"n\")", - "Fixed bug in readDouble()", - "Improved ensuref(), fixed nextLine to work in case of EOF, added startTest()", - "Supported \"partially correct\", example: quitf(_pc(13), \"result=%d\", result)", - "Added shuffle(begin, end), use it instead of random_shuffle(begin, end)", - "Added readLine(const string& ptrn), fixed the logic of readLine() in the validation mode", - "Package extended with samples of generators and validators", - "Written the documentation for classes and public methods in testlib.h", - "Implemented random routine to support generators, use registerGen() to switch it on", - "Implemented strict mode to validate tests, use registerValidation() to switch it on", - "Now ncmp.cpp and wcmp.cpp are return WA if answer is suffix or prefix of the output", - "Added InStream::readLong() and removed InStream::readLongint()", - "Now no footer added to each report by default (use directive FOOTER to switch on)", - "Now every checker has a name, use setName(const char* format, ...) to set it", - "Now it is compatible with TTS (by Kittens Computing)", - "Added \'ensure(condition, message = \"\")\' feature, it works like assert()", - "Fixed compatibility with MS C++ 7.1", - "Added footer with exit code information", - "Added compatibility with EJUDGE (compile with EJUDGE directive)" - }; +const char *latestFeatures[] = { + "Added ConstantBoundsLog, VariablesLog to validator testOverviewLogFile", + "Use setAppesModeEncoding to change xml encoding from windows-1251 to other", + "rnd.any/wany use distance/advance instead of -/+: now they support sets/multisets", + "Use syntax `int t = inf.readInt(1, 3, \"~t\");` to skip the lower bound check. Tildes can be used on either side or both: ~t, t~, ~t~", + "Supported EJUDGE support in registerTestlibCmd", + "Supported '--testMarkupFileName fn' and '--testCase tc/--testCaseFileName fn' for validators", + "Added opt defaults via opt(key/index, default_val); check unused opts when using has_opt or default opt (turn off this check with suppressEnsureNoUnusedOpt()).", + "For checker added --group and --testset command line params (like for validator), use checker.group() or checker.testset() to get values", + "Added quitpi(points_info, message) function to return with _points exit code 7 and given points_info", + "rnd.partition(size, sum[, min_part=1]) returns random (unsorted) partition which is a representation of the given `sum` as a sum of `size` positive integers (or >=min_part if specified)", + "rnd.distinct(size, n) and rnd.distinct(size, from, to)", + "opt(\"some_missing_key\") returns false now", + "has_opt(key)", + "Abort validator on validator.testset()/validator.group() if registered without using command line", + "Print integer range violations in a human readable way like `violates the range [1, 10^9]`", + "Opts supported: use them like n = opt(\"n\"), in a command line you can use an exponential notation", + "Reformatted", + "Use setTestCase(i) or unsetTestCase() to support test cases (you can use it in any type of program: generator, interactor, validator or checker)", + "Fixed issue #87: readStrictDouble accepts \"-0.00\"", + "Fixed issue #83: added InStream::quitif(condition, ...)", + "Fixed issue #79: fixed missed guard against repeated header include", + "Fixed issue #80: fixed UB in case of huge quitf message", + "Fixed issue #84: added readXs(size, indexBase = 1)", + "Fixed stringstream repeated usage issue", + "Fixed compilation in g++ (for std=c++03)", + "Batch of println functions (support collections, iterator ranges)", + "Introduced rnd.perm(size, first = 0) to generate a `first`-indexed permutation", + "Allow any whitespace in readInts-like functions for non-validators", + "Ignore 4+ command line arguments ifdef EJUDGE", + "Speed up of vtos", + "Show line number in validators in case of incorrect format", + "Truncate huge checker/validator/interactor message", + "Fixed issue with readTokenTo of very long tokens, now aborts with _pe/_fail depending of a stream type", + "Introduced InStream::ensure/ensuref checking a condition, returns wa/fail depending of a stream type", + "Fixed compilation in VS 2015+", + "Introduced space-separated read functions: readWords/readTokens, multilines read functions: readStrings/readLines", + "Introduced space-separated read functions: readInts/readIntegers/readLongs/readUnsignedLongs/readDoubles/readReals/readStrictDoubles/readStrictReals", + "Introduced split/tokenize functions to separate string by given char", + "Introduced InStream::readUnsignedLong and InStream::readLong with unsigned long long parameters", + "Supported --testOverviewLogFileName for validator: bounds hits + features", + "Fixed UB (sequence points) in random_t", + "POINTS_EXIT_CODE returned back to 7 (instead of 0)", + "Removed disable buffers for interactive problems, because it works unexpectedly in wine", + "InStream over string: constructor of InStream from base InStream to inherit policies and std::string", + "Added expectedButFound quit function, examples: expectedButFound(_wa, 10, 20), expectedButFound(_fail, ja, pa, \"[n=%d,m=%d]\", n, m)", + "Fixed incorrect interval parsing in patterns", + "Use registerGen(argc, argv, 1) to develop new generator, use registerGen(argc, argv, 0) to compile old generators (originally created for testlib under 0.8.7)", + "Introduced disableFinalizeGuard() to switch off finalization checkings", + "Use join() functions to format a range of items as a single string (separated by spaces or other separators)", + "Use -DENABLE_UNEXPECTED_EOF to enable special exit code (by default, 8) in case of unexpected eof. It is good idea to use it in interactors", + "Use -DUSE_RND_AS_BEFORE_087 to compile in compatibility mode with random behavior of versions before 0.8.7", + "Fixed bug with nan in stringToDouble", + "Fixed issue around overloads for size_t on x64", + "Added attribute 'points' to the XML output in case of result=_points", + "Exit codes can be customized via macros, e.g. -DPE_EXIT_CODE=14", + "Introduced InStream function readWordTo/readTokenTo/readStringTo/readLineTo for faster reading", + "Introduced global functions: format(), englishEnding(), upperCase(), lowerCase(), compress()", + "Manual buffer in InStreams, some IO speed improvements", + "Introduced quitif(bool, const char* pattern, ...) which delegates to quitf() in case of first argument is true", + "Introduced guard against missed quitf() in checker or readEof() in validators", + "Supported readStrictReal/readStrictDouble - to use in validators to check strictly float numbers", + "Supported registerInteraction(argc, argv)", + "Print checker message to the stderr instead of stdout", + "Supported TResult _points to output calculated score, use quitp(...) functions", + "Fixed to be compilable on Mac", + "PC_BASE_EXIT_CODE=50 in case of defined TESTSYS", + "Fixed issues 19-21, added __attribute__ format printf", + "Some bug fixes", + "ouf.readInt(1, 100) and similar calls return WA", + "Modified random_t to avoid integer overflow", + "Truncated checker output [patch by Stepan Gatilov]", + "Renamed class random -> class random_t", + "Supported name parameter for read-and-validation methods, like readInt(1, 2, \"n\")", + "Fixed bug in readDouble()", + "Improved ensuref(), fixed nextLine to work in case of EOF, added startTest()", + "Supported \"partially correct\", example: quitf(_pc(13), \"result=%d\", result)", + "Added shuffle(begin, end), use it instead of random_shuffle(begin, end)", + "Added readLine(const string& ptrn), fixed the logic of readLine() in the validation mode", + "Package extended with samples of generators and validators", + "Written the documentation for classes and public methods in testlib.h", + "Implemented random routine to support generators, use registerGen() to switch it on", + "Implemented strict mode to validate tests, use registerValidation() to switch it on", + "Now ncmp.cpp and wcmp.cpp are return WA if answer is suffix or prefix of the output", + "Added InStream::readLong() and removed InStream::readLongint()", + "Now no footer added to each report by default (use directive FOOTER to switch on)", + "Now every checker has a name, use setName(const char* format, ...) to set it", + "Now it is compatible with TTS (by Kittens Computing)", + "Added \'ensure(condition, message = \"\")\' feature, it works like assert()", + "Fixed compatibility with MS C++ 7.1", + "Added footer with exit code information", + "Added compatibility with EJUDGE (compile with EJUDGE directive)", + "Added compatibility with Contester (compile with CONTESTER directive)" +}; #ifdef _MSC_VER #define _CRT_SECURE_NO_DEPRECATE #define _CRT_SECURE_NO_WARNINGS +#define _CRT_NO_VA_START_VALIDATION #endif /* Overrides random() for Borland C++. */ @@ -139,52 +183,79 @@ const char* latestFeatures[] = { #include #include #include +#include +#include #include +#include +#include #include #include #include #include #include - #include +#include +#include #ifdef __EMSCRIPTEN__ #include #endif -#if ( _WIN32 || __WIN32__ || _WIN64 || __WIN64__ ) -# if !defined(_MSC_VER) || _MSC_VER>1400 +#ifdef TESTLIB_THROW_EXIT_EXCEPTION_INSTEAD_OF_EXIT +# include +#endif + +#if (_WIN32 || __WIN32__ || __WIN32 || _WIN64 || __WIN64__ || __WIN64 || WINNT || __WINNT || __WINNT__ || __CYGWIN__) +# if !defined(_MSC_VER) || _MSC_VER > 1400 +# define NOMINMAX 1 # include # else # define WORD unsigned short +# include # endif # include # define ON_WINDOWS +# if defined(_MSC_VER) && _MSC_VER > 1400 +# pragma warning( disable : 4127 ) +# pragma warning( disable : 4146 ) +# pragma warning( disable : 4458 ) +# endif #else # define WORD unsigned short +# include #endif -#ifdef linux -#include +#if defined(FOR_WINDOWS) && defined(FOR_LINUX) +#error Only one target system is allowed #endif #ifndef LLONG_MIN #define LLONG_MIN (-9223372036854775807LL - 1) #endif +#ifndef ULLONG_MAX +#define ULLONG_MAX (18446744073709551615) +#endif + #define LF ((char)10) #define CR ((char)13) #define TAB ((char)9) #define SPACE ((char)' ') -#define EOFC ((char)26) +#define EOFC (255) #ifndef OK_EXIT_CODE -# define OK_EXIT_CODE 0 +# ifdef CONTESTER +# define OK_EXIT_CODE 0xAC +# else +# define OK_EXIT_CODE 0 +# endif #endif #ifndef WA_EXIT_CODE # ifdef EJUDGE # define WA_EXIT_CODE 5 +# elif defined(CONTESTER) +# define WA_EXIT_CODE 0xAB # else # define WA_EXIT_CODE 1 # endif @@ -193,6 +264,8 @@ const char* latestFeatures[] = { #ifndef PE_EXIT_CODE # ifdef EJUDGE # define PE_EXIT_CODE 4 +# elif defined(CONTESTER) +# define PE_EXIT_CODE 0xAA # else # define PE_EXIT_CODE 2 # endif @@ -201,6 +274,8 @@ const char* latestFeatures[] = { #ifndef FAIL_EXIT_CODE # ifdef EJUDGE # define FAIL_EXIT_CODE 6 +# elif defined(CONTESTER) +# define FAIL_EXIT_CODE 0xA3 # else # define FAIL_EXIT_CODE 3 # endif @@ -215,7 +290,7 @@ const char* latestFeatures[] = { #endif #ifndef POINTS_EXIT_CODE -# define POINTS_EXIT_CODE 0 +# define POINTS_EXIT_CODE 7 #endif #ifndef UNEXPECTED_EOF_EXIT_CODE @@ -261,35 +336,78 @@ static int __testlib_format_buffer_usage_count = 0; __testlib_format_buffer_usage_count++; \ va_list ap; \ va_start(ap, fmt); \ - std::vsprintf(__testlib_format_buffer, cstr, ap); \ + vsnprintf(__testlib_format_buffer, sizeof(__testlib_format_buffer), cstr, ap); \ va_end(ap); \ + __testlib_format_buffer[sizeof(__testlib_format_buffer) - 1] = 0; \ result = std::string(__testlib_format_buffer); \ __testlib_format_buffer_usage_count--; \ +#ifdef __GNUC__ +__attribute__ ((format (printf, 1, 2))) +#endif +std::string testlib_format_(const char *fmt, ...); +std::string testlib_format_(const std::string fmt, ...); + const long long __TESTLIB_LONGLONG_MAX = 9223372036854775807LL; +const int __TESTLIB_MAX_TEST_CASE = 1073741823; + +int __testlib_exitCode; -NORETURN static void __testlib_fail(const std::string& message); +bool __testlib_hasTestCase; +int __testlib_testCase = -1; + +void setTestCase(int testCase); + +void unsetTestCase() { + __testlib_hasTestCase = false; + __testlib_testCase = -1; +} + +NORETURN static void __testlib_fail(const std::string &message); template -static inline T __testlib_abs(const T& x) -{ +#ifdef __GNUC__ +__attribute__((const)) +#endif +static inline T __testlib_abs(const T &x) { return x > 0 ? x : -x; } template -static inline T __testlib_min(const T& a, const T& b) -{ +#ifdef __GNUC__ +__attribute__((const)) +#endif +static inline T __testlib_min(const T &a, const T &b) { return a < b ? a : b; } template -static inline T __testlib_max(const T& a, const T& b) -{ +#ifdef __GNUC__ +__attribute__((const)) +#endif +static inline T __testlib_max(const T &a, const T &b) { return a > b ? a : b; } -static bool __testlib_prelimIsNaN(double r) -{ +template +#ifdef __GNUC__ +__attribute__((const)) +#endif +static inline T __testlib_crop(T value, T a, T b) { + return __testlib_min(__testlib_max(value, a), --b); +} + +#ifdef __GNUC__ +__attribute__((const)) +#endif +static inline double __testlib_crop(double value, double a, double b) { + value = __testlib_min(__testlib_max(value, a), b); + if (value >= b) + value = std::nexttoward(b, a); + return value; +} + +static bool __testlib_prelimIsNaN(double r) { volatile double ra = r; #ifndef __BORLANDC__ return ((ra != ra) == true) && ((ra == ra) == false) && ((1.0 > ra) == false) && ((1.0 < ra) == false); @@ -298,102 +416,316 @@ static bool __testlib_prelimIsNaN(double r) #endif } -static std::string removeDoubleTrailingZeroes(std::string value) -{ +#ifdef __GNUC__ +__attribute__((const)) +#endif +static std::string removeDoubleTrailingZeroes(std::string value) { while (!value.empty() && value[value.length() - 1] == '0' && value.find('.') != std::string::npos) value = value.substr(0, value.length() - 1); - return value + '0'; + if (!value.empty() && value[value.length() - 1] == '.') + return value + '0'; + else + return value; } #ifdef __GNUC__ -__attribute__ ((format (printf, 1, 2))) +__attribute__((const)) #endif -std::string format(const char* fmt, ...) -{ - FMT_TO_RESULT(fmt, fmt, result); - return result; +inline std::string upperCase(std::string s) { + for (size_t i = 0; i < s.length(); i++) + if ('a' <= s[i] && s[i] <= 'z') + s[i] = char(s[i] - 'a' + 'A'); + return s; } -std::string format(const std::string& fmt, ...) -{ - FMT_TO_RESULT(fmt, fmt.c_str(), result); - return result; +#ifdef __GNUC__ +__attribute__((const)) +#endif +inline std::string lowerCase(std::string s) { + for (size_t i = 0; i < s.length(); i++) + if ('A' <= s[i] && s[i] <= 'Z') + s[i] = char(s[i] - 'A' + 'a'); + return s; } -static std::string __testlib_part(const std::string& s); +#ifdef __GNUC__ +__attribute__((const)) +#endif +static std::string __testlib_part(const std::string &s); -static bool __testlib_isNaN(double r) -{ +static bool __testlib_isNaN(double r) { __TESTLIB_STATIC_ASSERT(sizeof(double) == sizeof(long long)); volatile double ra = r; long long llr1, llr2; - std::memcpy((void*)&llr1, (void*)&ra, sizeof(double)); + std::memcpy((void *) &llr1, (void *) &ra, sizeof(double)); ra = -ra; - std::memcpy((void*)&llr2, (void*)&ra, sizeof(double)); - long long llnan = 0xFFF8000000000000ll; + std::memcpy((void *) &llr2, (void *) &ra, sizeof(double)); + long long llnan = 0xFFF8000000000000LL; return __testlib_prelimIsNaN(r) || llnan == llr1 || llnan == llr2; } -static double __testlib_nan() -{ +static double __testlib_nan() { __TESTLIB_STATIC_ASSERT(sizeof(double) == sizeof(long long)); - long long llnan = 0xFFF8000000000000ll; +#ifndef NAN + long long llnan = 0xFFF8000000000000LL; double nan; std::memcpy(&nan, &llnan, sizeof(double)); return nan; +#else + return NAN; +#endif } -static bool __testlib_isInfinite(double r) -{ +static bool __testlib_isInfinite(double r) { volatile double ra = r; - return (ra > 1E100 || ra < -1E100); + return (ra > 1E300 || ra < -1E300); } -static void __testlib_set_binary(std::FILE* file) -{ -#ifdef O_BINARY - if (NULL != file) - { -#ifndef __BORLANDC__ - _setmode(_fileno(file), O_BINARY); -#else - setmode(fileno(file), O_BINARY); +#ifdef __GNUC__ +__attribute__((const)) +#endif +inline bool doubleCompare(double expected, double result, double MAX_DOUBLE_ERROR) { + MAX_DOUBLE_ERROR += 1E-15; + if (__testlib_isNaN(expected)) { + return __testlib_isNaN(result); + } else if (__testlib_isInfinite(expected)) { + if (expected > 0) { + return result > 0 && __testlib_isInfinite(result); + } else { + return result < 0 && __testlib_isInfinite(result); + } + } else if (__testlib_isNaN(result) || __testlib_isInfinite(result)) { + return false; + } else if (__testlib_abs(result - expected) <= MAX_DOUBLE_ERROR) { + return true; + } else { + double minv = __testlib_min(expected * (1.0 - MAX_DOUBLE_ERROR), + expected * (1.0 + MAX_DOUBLE_ERROR)); + double maxv = __testlib_max(expected * (1.0 - MAX_DOUBLE_ERROR), + expected * (1.0 + MAX_DOUBLE_ERROR)); + return result >= minv && result <= maxv; + } +} + +#ifdef __GNUC__ +__attribute__((const)) +#endif +inline double doubleDelta(double expected, double result) { + double absolute = __testlib_abs(result - expected); + + if (__testlib_abs(expected) > 1E-9) { + double relative = __testlib_abs(absolute / expected); + return __testlib_min(absolute, relative); + } else + return absolute; +} + +/** It does nothing on non-windows and files differ from stdin/stdout/stderr. */ +static void __testlib_set_binary(std::FILE *file) { + if (NULL != file) { +#ifdef ON_WINDOWS +# ifdef _O_BINARY + if (stdin == file) +# ifdef STDIN_FILENO + return void(_setmode(STDIN_FILENO, _O_BINARY)); +# else + return void(_setmode(_fileno(stdin), _O_BINARY)); +# endif + if (stdout == file) +# ifdef STDOUT_FILENO + return void(_setmode(STDOUT_FILENO, _O_BINARY)); +# else + return void(_setmode(_fileno(stdout), _O_BINARY)); +# endif + if (stderr == file) +# ifdef STDERR_FILENO + return void(_setmode(STDERR_FILENO, _O_BINARY)); +# else + return void(_setmode(_fileno(stderr), _O_BINARY)); +# endif +# elif O_BINARY + if (stdin == file) +# ifdef STDIN_FILENO + return void(setmode(STDIN_FILENO, O_BINARY)); +# else + return void(setmode(fileno(stdin), O_BINARY)); +# endif + if (stdout == file) +# ifdef STDOUT_FILENO + return void(setmode(STDOUT_FILENO, O_BINARY)); +# else + return void(setmode(fileno(stdout), O_BINARY)); +# endif + if (stderr == file) +# ifdef STDERR_FILENO + return void(setmode(STDERR_FILENO, O_BINARY)); +# else + return void(setmode(fileno(stderr), O_BINARY)); +# endif +# endif #endif } +} + +#if __cplusplus > 199711L || defined(_MSC_VER) +template +#ifdef __GNUC__ +__attribute__((const)) #endif +static std::string vtos(const T &t, std::true_type) { + if (t == 0) + return "0"; + else { + T n(t); + bool negative = n < 0; + std::string s; + while (n != 0) { + T digit = n % 10; + if (digit < 0) + digit = -digit; + s += char('0' + digit); + n /= 10; + } + std::reverse(s.begin(), s.end()); + return negative ? "-" + s : s; + } +} + +template +static std::string vtos(const T &t, std::false_type) { + std::string s; + static std::stringstream ss; + ss.str(std::string()); + ss.clear(); + ss << t; + ss >> s; + return s; +} + +template +static std::string vtos(const T &t) { + return vtos(t, std::is_integral()); +} + +/* signed case. */ +template +static std::string toHumanReadableString(const T &n, std::false_type) { + if (n == 0) + return vtos(n); + int trailingZeroCount = 0; + T n_ = n; + while (n_ % 10 == 0) + n_ /= 10, trailingZeroCount++; + if (trailingZeroCount >= 7) { + if (n_ == 1) + return "10^" + vtos(trailingZeroCount); + else if (n_ == -1) + return "-10^" + vtos(trailingZeroCount); + else + return vtos(n_) + "*10^" + vtos(trailingZeroCount); + } else + return vtos(n); +} + +/* unsigned case. */ +template +static std::string toHumanReadableString(const T &n, std::true_type) { + if (n == 0) + return vtos(n); + int trailingZeroCount = 0; + T n_ = n; + while (n_ % 10 == 0) + n_ /= 10, trailingZeroCount++; + if (trailingZeroCount >= 7) { + if (n_ == 1) + return "10^" + vtos(trailingZeroCount); + else + return vtos(n_) + "*10^" + vtos(trailingZeroCount); + } else + return vtos(n); } -static FILE *__testlib_fopen(const char *name, const char *mode) +template +static std::string toHumanReadableString(const T &n) { + return toHumanReadableString(n, std::is_unsigned()); +} +#else +template +static std::string vtos(const T& t) { -#ifdef __EMSCRIPTEN__ - EM_ASM( - try { - FS.stat('/cwd'); - } catch (e) { - FS.mkdir('/cwd'); - FS.mount(NODEFS, { root: '.' }, '/cwd'); - } - ); - return fopen((std::string("/cwd/") + name).c_str(), mode); + std::string s; + static std::stringstream ss; + ss.str(std::string()); + ss.clear(); + ss << t; + ss >> s; + return s; +} + +template +static std::string toHumanReadableString(const T &n) { + return vtos(n); +} +#endif + +template +static std::string toString(const T &t) { + return vtos(t); +} + +#if __cplusplus > 199711L || defined(_MSC_VER) +/* opts */ +void prepareOpts(int argc, char* argv[]); +#endif + +FILE* testlib_fopen_(const char* path, const char* mode) { +#if defined (_MSC_VER) + FILE* result = NULL; + if (fopen_s(&result, path, mode) != 0) + return NULL; + else + return result; +#elif defined (__EMSCRIPTEN__) + EM_ASM( + try { + FS.stat('/cwd'); + } catch (e) { + FS.mkdir('/cwd'); + FS.mount(NODEFS, { root: '.' }, '/cwd'); + } + ); + return fopen((std::string("/cwd/") + name).c_str(), mode); +#else + return std::fopen(path, mode); +#endif +} + +FILE* testlib_freopen_(const char* path, const char* mode, FILE* file) { +#ifdef _MSC_VER + FILE* result = NULL; + if (freopen_s(&result, path, mode, file) != 0) + return NULL; + else + return result; #else - return fopen(name, mode); + return std::freopen(path, mode, file); #endif } /* * Very simple regex-like pattern. * It used for two purposes: validation and generation. - * + * * For example, pattern("[a-z]{1,5}").next(rnd) will return - * random string from lowercase latin letters with length - * from 1 to 5. It is easier to call rnd.next("[a-z]{1,5}") - * for the same effect. - * + * random string from lowercase latin letters with length + * from 1 to 5. It is easier to call rnd.next("[a-z]{1,5}") + * for the same effect. + * * Another samples: * "mike|john" will generate (match) "mike" or "john"; * "-?[1-9][0-9]{0,3}" will generate (match) non-zero integers from -9999 to 9999; * "id-([ac]|b{2})" will generate (match) "id-a", "id-bb", "id-c"; - * "[^0-9]*" will match sequences (empty or non-empty) without digits, you can't + * "[^0-9]*" will match sequences (empty or non-empty) without digits, you can't * use it for generations. * * You can't use pattern for generation if it contains meta-symbol '*'. Also it @@ -401,31 +733,38 @@ static FILE *__testlib_fopen(const char *name, const char *mode) * * For matching very simple greedy algorithm is used. For example, pattern * "[0-9]?1" will not match "1", because of greedy nature of matching. - * Alternations (meta-symbols "|") are processed with brute-force algorithm, so + * Alternations (meta-symbols "|") are processed with brute-force algorithm, so * do not use many alternations in one expression. * * If you want to use one expression many times it is better to compile it into - * a single pattern like "pattern p("[a-z]+")". Later you can use + * a single pattern like "pattern p("[a-z]+")". Later you can use * "p.matches(std::string s)" or "p.next(random_t& rd)" to check matching or generate * new string by pattern. - * + * * Simpler way to read token and check it for pattern matching is "inf.readToken("[a-z]+")". + * + * All spaces are ignored in regex, unless escaped with \. For example, ouf.readLine("NO SOLUTION") + * will expect "NOSOLUTION", the correct call should be ouf.readLine("NO\\ SOLUTION") or + * ouf.readLine(R"(NO\ SOLUTION)") if you prefer raw string literals from C++11. */ class random_t; -class pattern -{ +class pattern { public: /* Create pattern instance by string. */ pattern(std::string s); + /* Generate new string by pattern and given random_t. */ - std::string next(random_t& rnd) const; + std::string next(random_t &rnd) const; + /* Checks if given string match the pattern. */ - bool matches(const std::string& s) const; + bool matches(const std::string &s) const; + /* Returns source string of the pattern. */ std::string src() const; + private: - bool matches(const std::string& s, size_t pos) const; + bool matches(const std::string &s, size_t pos) const; std::string s; std::vector children; @@ -434,9 +773,9 @@ class pattern int to; }; -/* - * Use random_t instances to generate random values. It is preffered - * way to use randoms instead of rand() function or self-written +/* + * Use random_t instances to generate random values. It is preferred + * way to use randoms instead of rand() function or self-written * randoms. * * Testlib defines global variable "rnd" of random_t class. @@ -446,8 +785,7 @@ class pattern * Random generates uniformly distributed values if another strategy is * not specified explicitly. */ -class random_t -{ +class random_t { private: unsigned long long seed; static const unsigned long long multiplier; @@ -455,20 +793,20 @@ class random_t static const unsigned long long mask; static const int lim; - long long nextBits(int bits) - { - if (bits <= 48) - { + long long nextBits(int bits) { + if (bits <= 48) { seed = (seed * multiplier + addend) & mask; - return (long long)(seed >> (48 - bits)); - } - else - { + return (long long) (seed >> (48 - bits)); + } else { if (bits > 63) __testlib_fail("random_t::nextBits(int bits): n must be less than 64"); int lowerBitCount = (random_t::version == 0 ? 31 : 32); - return ((nextBits(31) << 32) ^ nextBits(lowerBitCount)); + + long long left = (nextBits(31) << 32); + long long right = nextBits(lowerBitCount); + + return left ^ right; } } @@ -477,41 +815,38 @@ class random_t /* New random_t with fixed seed. */ random_t() - : seed(3905348978240129619LL) - { + : seed(3905348978240129619LL) { } /* Sets seed by command line. */ - void setSeed(int argc, char* argv[]) - { + void setSeed(int argc, char *argv[]) { random_t p; seed = 3905348978240129619LL; - for (int i = 1; i < argc; i++) - { + for (int i = 1; i < argc; i++) { std::size_t le = std::strlen(argv[i]); for (std::size_t j = 0; j < le; j++) - seed = seed * multiplier + (unsigned int)(argv[i][j]) + addend; + seed = seed * multiplier + (unsigned int) (argv[i][j]) + addend; seed += multiplier / addend; } seed = seed & mask; } - /* Sets seed by given value. */ - void setSeed(long long _seed) - { - _seed = (_seed ^ multiplier) & mask; - seed = _seed; + /* Sets seed by given value. */ + void setSeed(long long _seed) { + seed = (unsigned long long) _seed; + seed = (seed ^ multiplier) & mask; } #ifndef __BORLANDC__ + /* Random string value by given pattern (see pattern documentation). */ - std::string next(const std::string& ptrn) - { + std::string next(const std::string &ptrn) { pattern p(ptrn); return p.next(*this); } + #else /* Random string value by given pattern (see pattern documentation). */ std::string next(std::string ptrn) @@ -522,16 +857,15 @@ class random_t #endif /* Random value in range [0, n-1]. */ - int next(int n) - { + int next(int n) { if (n <= 0) __testlib_fail("random_t::next(int n): n must be positive"); if ((n & -n) == n) // n is a power of 2 - return (int)((n * (long long)nextBits(31)) >> 31); + return (int) ((n * (long long) nextBits(31)) >> 31); const long long limit = INT_MAX / n * n; - + long long bits; do { bits = nextBits(31); @@ -541,21 +875,19 @@ class random_t } /* Random value in range [0, n-1]. */ - unsigned int next(unsigned int n) - { + unsigned int next(unsigned int n) { if (n >= INT_MAX) __testlib_fail("random_t::next(unsigned int n): n must be less INT_MAX"); - return (unsigned int)next(int(n)); + return (unsigned int) next(int(n)); } /* Random value in range [0, n-1]. */ - long long next(long long n) - { + long long next(long long n) { if (n <= 0) __testlib_fail("random_t::next(long long n): n must be positive"); const long long limit = __TESTLIB_LONGLONG_MAX / n * n; - + long long bits; do { bits = nextBits(63); @@ -565,116 +897,111 @@ class random_t } /* Random value in range [0, n-1]. */ - unsigned long long next(unsigned long long n) - { - if (n >= (unsigned long long)(__TESTLIB_LONGLONG_MAX)) + unsigned long long next(unsigned long long n) { + if (n >= (unsigned long long) (__TESTLIB_LONGLONG_MAX)) __testlib_fail("random_t::next(unsigned long long n): n must be less LONGLONG_MAX"); - return (unsigned long long)next((long long)(n)); + return (unsigned long long) next((long long) (n)); } /* Random value in range [0, n-1]. */ - long next(long n) - { - return (long)next((long long)(n)); + long next(long n) { + return (long) next((long long) (n)); } /* Random value in range [0, n-1]. */ - unsigned long next(unsigned long n) - { - if (n >= (unsigned long)(LONG_MAX)) + unsigned long next(unsigned long n) { + if (n >= (unsigned long) (LONG_MAX)) __testlib_fail("random_t::next(unsigned long n): n must be less LONG_MAX"); - return (unsigned long)next((unsigned long long)(n)); + return (unsigned long) next((unsigned long long) (n)); } /* Returns random value in range [from,to]. */ - int next(int from, int to) - { - return int(next((long long)to - from + 1) + from); + int next(int from, int to) { + return int(next((long long) to - from + 1) + from); } /* Returns random value in range [from,to]. */ - unsigned int next(unsigned int from, unsigned int to) - { - return (unsigned int)(next((long long)to - from + 1) + from); + unsigned int next(unsigned int from, unsigned int to) { + return (unsigned int) (next((long long) to - from + 1) + from); } /* Returns random value in range [from,to]. */ - long long next(long long from, long long to) - { + long long next(long long from, long long to) { return next(to - from + 1) + from; } /* Returns random value in range [from,to]. */ - unsigned long long next(unsigned long long from, unsigned long long to) - { + unsigned long long next(unsigned long long from, unsigned long long to) { if (from > to) __testlib_fail("random_t::next(unsigned long long from, unsigned long long to): from can't not exceed to"); return next(to - from + 1) + from; } /* Returns random value in range [from,to]. */ - long next(long from, long to) - { + long next(long from, long to) { return next(to - from + 1) + from; } /* Returns random value in range [from,to]. */ - unsigned long next(unsigned long from, unsigned long to) - { + unsigned long next(unsigned long from, unsigned long to) { if (from > to) __testlib_fail("random_t::next(unsigned long from, unsigned long to): from can't not exceed to"); return next(to - from + 1) + from; } /* Random double value in range [0, 1). */ - double next() - { - return (double)(((long long)(nextBits(26)) << 27) + nextBits(27)) / (double)(1LL << 53); + double next() { + long long left = ((long long) (nextBits(26)) << 27); + long long right = nextBits(27); + return __testlib_crop((double) (left + right) / (double) (1LL << 53), 0.0, 1.0); } /* Random double value in range [0, n). */ - double next(double n) - { - return n * next(); + double next(double n) { + if (n <= 0.0) + __testlib_fail("random_t::next(double): n should be positive"); + return __testlib_crop(n * next(), 0.0, n); } /* Random double value in range [from, to). */ - double next(double from, double to) - { + double next(double from, double to) { + if (from >= to) + __testlib_fail("random_t::next(double from, double to): from should be strictly less than to"); return next(to - from) + from; } /* Returns random element from container. */ - template - typename Container::value_type any(const Container& c) - { - size_t size = c.size(); + template + typename Container::value_type any(const Container &c) { + int size = int(c.size()); if (size <= 0) __testlib_fail("random_t::any(const Container& c): c.size() must be positive"); - return *(c.begin() + next(size)); + typename Container::const_iterator it = c.begin(); + std::advance(it, next(size)); + return *it; } /* Returns random element from iterator range. */ - template - typename Iter::value_type any(const Iter& begin, const Iter& end) - { - int size = int(end - begin); + template + typename Iter::value_type any(const Iter &begin, const Iter &end) { + int size = static_cast(std::distance(begin, end)); if (size <= 0) __testlib_fail("random_t::any(const Iter& begin, const Iter& end): range must have positive length"); - return *(begin + next(size)); + Iter it = begin; + std::advance(it, next(size)); + return *it; } /* Random string value by given pattern (see pattern documentation). */ #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))) #endif - std::string next(const char* format, ...) - { + std::string next(const char *format, ...) { FMT_TO_RESULT(format, format, ptrn); return next(ptrn); } - /* + /* * Weighted next. If type == 0 than it is usual "next()". * * If type = 1, than it returns "max(next(), next())" @@ -682,222 +1009,317 @@ class random_t * * If type < 0, than "max" function replaces with "min". */ - int wnext(int n, int type) - { + int wnext(int n, int type) { if (n <= 0) __testlib_fail("random_t::wnext(int n, int type): n must be positive"); - - if (abs(type) < random_t::lim) - { + + if (abs(type) < random_t::lim) { int result = next(n); for (int i = 0; i < +type; i++) result = __testlib_max(result, next(n)); - + for (int i = 0; i < -type; i++) result = __testlib_min(result, next(n)); return result; - } - else - { + } else { double p; - + if (type > 0) p = std::pow(next() + 0.0, 1.0 / (type + 1)); else p = 1 - std::pow(next() + 0.0, 1.0 / (-type + 1)); - return int(n * p); + return __testlib_crop((int) (double(n) * p), 0, n); } } - + /* See wnext(int, int). It uses the same algorithms. */ - long long wnext(long long n, int type) - { + long long wnext(long long n, int type) { if (n <= 0) __testlib_fail("random_t::wnext(long long n, int type): n must be positive"); - - if (abs(type) < random_t::lim) - { + + if (abs(type) < random_t::lim) { long long result = next(n); for (int i = 0; i < +type; i++) result = __testlib_max(result, next(n)); - + for (int i = 0; i < -type; i++) result = __testlib_min(result, next(n)); return result; - } - else - { + } else { double p; - - if (type > 0) - p = std::pow(next() + 0.0, 1.0 / (type + 1)); - else - p = std::pow(next() + 0.0, - type + 1); - return __testlib_min(__testlib_max((long long)(double(n) * p), 0LL), n - 1LL); - } - } - - /* See wnext(int, int). It uses the same algorithms. */ - double wnext(int type) - { - if (abs(type) < random_t::lim) - { - double result = next(); - - for (int i = 0; i < +type; i++) - result = __testlib_max(result, next()); - - for (int i = 0; i < -type; i++) - result = __testlib_min(result, next()); - - return result; - } - else - { - double p; - if (type > 0) p = std::pow(next() + 0.0, 1.0 / (type + 1)); else - p = std::pow(next() + 0.0, - type + 1); + p = 1 - std::pow(next() + 0.0, 1.0 / (-type + 1)); - return p; + return __testlib_crop((long long) (double(n) * p), 0LL, n); } } - - /* See wnext(int, int). It uses the same algorithms. */ - double wnext(double n, int type) - { + + /* Returns value in [0, n). See wnext(int, int). It uses the same algorithms. */ + double wnext(double n, int type) { if (n <= 0) __testlib_fail("random_t::wnext(double n, int type): n must be positive"); - if (abs(type) < random_t::lim) - { + if (abs(type) < random_t::lim) { double result = next(); for (int i = 0; i < +type; i++) result = __testlib_max(result, next()); - + for (int i = 0; i < -type; i++) result = __testlib_min(result, next()); return n * result; - } - else - { + } else { double p; - + if (type > 0) p = std::pow(next() + 0.0, 1.0 / (type + 1)); else - p = std::pow(next() + 0.0, - type + 1); + p = 1 - std::pow(next() + 0.0, 1.0 / (-type + 1)); - return n * p; + return __testlib_crop(n * p, 0.0, n); } } + /* Returns value in [0, 1). See wnext(int, int). It uses the same algorithms. */ + double wnext(int type) { + return wnext(1.0, type); + } + /* See wnext(int, int). It uses the same algorithms. */ - unsigned int wnext(unsigned int n, int type) - { + unsigned int wnext(unsigned int n, int type) { if (n >= INT_MAX) __testlib_fail("random_t::wnext(unsigned int n, int type): n must be less INT_MAX"); - return (unsigned int)wnext(int(n), type); + return (unsigned int) wnext(int(n), type); } - + /* See wnext(int, int). It uses the same algorithms. */ - unsigned long long wnext(unsigned long long n, int type) - { - if (n >= (unsigned long long)(__TESTLIB_LONGLONG_MAX)) + unsigned long long wnext(unsigned long long n, int type) { + if (n >= (unsigned long long) (__TESTLIB_LONGLONG_MAX)) __testlib_fail("random_t::wnext(unsigned long long n, int type): n must be less LONGLONG_MAX"); - return (unsigned long long)wnext((long long)(n), type); + return (unsigned long long) wnext((long long) (n), type); } /* See wnext(int, int). It uses the same algorithms. */ - long wnext(long n, int type) - { - return (long)wnext((long long)(n), type); + long wnext(long n, int type) { + return (long) wnext((long long) (n), type); } - + /* See wnext(int, int). It uses the same algorithms. */ - unsigned long wnext(unsigned long n, int type) - { - if (n >= (unsigned long)(LONG_MAX)) + unsigned long wnext(unsigned long n, int type) { + if (n >= (unsigned long) (LONG_MAX)) __testlib_fail("random_t::wnext(unsigned long n, int type): n must be less LONG_MAX"); - return (unsigned long)wnext((unsigned long long)(n), type); + return (unsigned long) wnext((unsigned long long) (n), type); } /* Returns weighted random value in range [from, to]. */ - int wnext(int from, int to, int type) - { + int wnext(int from, int to, int type) { + if (from > to) + __testlib_fail("random_t::wnext(int from, int to, int type): from can't not exceed to"); return wnext(to - from + 1, type) + from; } - + /* Returns weighted random value in range [from, to]. */ - int wnext(unsigned int from, unsigned int to, int type) - { + int wnext(unsigned int from, unsigned int to, int type) { + if (from > to) + __testlib_fail("random_t::wnext(unsigned int from, unsigned int to, int type): from can't not exceed to"); return int(wnext(to - from + 1, type) + from); } - + /* Returns weighted random value in range [from, to]. */ - long long wnext(long long from, long long to, int type) - { + long long wnext(long long from, long long to, int type) { + if (from > to) + __testlib_fail("random_t::wnext(long long from, long long to, int type): from can't not exceed to"); return wnext(to - from + 1, type) + from; } - + /* Returns weighted random value in range [from, to]. */ - unsigned long long wnext(unsigned long long from, unsigned long long to, int type) - { + unsigned long long wnext(unsigned long long from, unsigned long long to, int type) { if (from > to) - __testlib_fail("random_t::wnext(unsigned long long from, unsigned long long to, int type): from can't not exceed to"); + __testlib_fail( + "random_t::wnext(unsigned long long from, unsigned long long to, int type): from can't not exceed to"); return wnext(to - from + 1, type) + from; } - + /* Returns weighted random value in range [from, to]. */ - long wnext(long from, long to, int type) - { + long wnext(long from, long to, int type) { + if (from > to) + __testlib_fail("random_t::wnext(long from, long to, int type): from can't not exceed to"); return wnext(to - from + 1, type) + from; } - + /* Returns weighted random value in range [from, to]. */ - unsigned long wnext(unsigned long from, unsigned long to, int type) - { + unsigned long wnext(unsigned long from, unsigned long to, int type) { if (from > to) __testlib_fail("random_t::wnext(unsigned long from, unsigned long to, int type): from can't not exceed to"); return wnext(to - from + 1, type) + from; } - + /* Returns weighted random double value in range [from, to). */ - double wnext(double from, double to, int type) - { + double wnext(double from, double to, int type) { + if (from >= to) + __testlib_fail("random_t::wnext(double from, double to, int type): from should be strictly less than to"); return wnext(to - from, type) + from; } /* Returns weighted random element from container. */ - template - typename Container::value_type wany(const Container& c, int type) - { - size_t size = c.size(); + template + typename Container::value_type wany(const Container &c, int type) { + int size = int(c.size()); if (size <= 0) __testlib_fail("random_t::wany(const Container& c, int type): c.size() must be positive"); - return *(c.begin() + wnext(size, type)); + typename Container::const_iterator it = c.begin(); + std::advance(it, wnext(size, type)); + return *it; } /* Returns weighted random element from iterator range. */ - template - typename Iter::value_type wany(const Iter& begin, const Iter& end, int type) - { - int size = int(end - begin); + template + typename Iter::value_type wany(const Iter &begin, const Iter &end, int type) { + int size = static_cast(std::distance(begin, end)); if (size <= 0) - __testlib_fail("random_t::any(const Iter& begin, const Iter& end, int type): range must have positive length"); - return *(begin + wnext(size, type)); + __testlib_fail( + "random_t::any(const Iter& begin, const Iter& end, int type): range must have positive length"); + Iter it = begin; + std::advance(it, wnext(size, type)); + return *it; + } + + /* Returns random permutation of the given size (values are between `first` and `first`+size-1)*/ + template + std::vector perm(T size, E first) { + if (size < 0) + __testlib_fail("random_t::perm(T size, E first = 0): size must non-negative"); + else if (size == 0) + return std::vector(); + std::vector p(size); + E current = first; + for (T i = 0; i < size; i++) + p[i] = current++; + if (size > 1) + for (T i = 1; i < size; i++) + std::swap(p[i], p[next(i + 1)]); + return p; + } + + /* Returns random permutation of the given size (values are between 0 and size-1)*/ + template + std::vector perm(T size) { + return perm(size, T(0)); + } + + /* Returns `size` unordered (unsorted) distinct numbers between `from` and `to`. */ + template + std::vector distinct(int size, T from, T to) { + std::vector result; + if (size == 0) + return result; + + if (from > to) + __testlib_fail("random_t::distinct expected from <= to"); + + if (size < 0) + __testlib_fail("random_t::distinct expected size >= 0"); + + uint64_t n = to - from + 1; + if (uint64_t(size) > n) + __testlib_fail("random_t::distinct expected size <= to - from + 1"); + + double expected = 0.0; + for (int i = 1; i <= size; i++) + expected += double(n) / double(n - i + 1); + + if (expected < double(n)) { + std::set vals; + while (int(vals.size()) < size) { + T x = T(next(from, to)); + if (vals.insert(x).second) + result.push_back(x); + } + } else { + if (n > 1000000000) + __testlib_fail("random_t::distinct here expected to - from + 1 <= 1000000000"); + std::vector p(perm(int(n), from)); + result.insert(result.end(), p.begin(), p.begin() + size); + } + + return result; + } + + /* Returns `size` unordered (unsorted) distinct numbers between `0` and `upper`-1. */ + template + std::vector distinct(int size, T upper) { + if (size < 0) + __testlib_fail("random_t::distinct expected size >= 0"); + if (size == 0) + return std::vector(); + + if (upper <= 0) + __testlib_fail("random_t::distinct expected upper > 0"); + if (size > upper) + __testlib_fail("random_t::distinct expected size <= upper"); + + return distinct(size, T(0), upper - 1); + } + + /* Returns random (unsorted) partition which is a representation of sum as a sum of integers not less than min_part. */ + template + std::vector partition(int size, T sum, T min_part) { + if (size < 0) + __testlib_fail("random_t::partition: size < 0"); + if (size == 0 && sum != 0) + __testlib_fail("random_t::partition: size == 0 && sum != 0"); + if (min_part * size > sum) + __testlib_fail("random_t::partition: min_part * size > sum"); + if (size == 0 && sum == 0) + return std::vector(); + + T sum_ = sum; + sum -= min_part * size; + + std::vector septums(size); + std::vector d = distinct(size - 1, T(1), T(sum + size - 1)); + for (int i = 0; i + 1 < size; i++) + septums[i + 1] = d[i]; + sort(septums.begin(), septums.end()); + + std::vector result(size); + for (int i = 0; i + 1 < size; i++) + result[i] = septums[i + 1] - septums[i] - 1; + result[size - 1] = sum + size - 1 - septums.back(); + + for (std::size_t i = 0; i < result.size(); i++) + result[i] += min_part; + + T result_sum = 0; + for (std::size_t i = 0; i < result.size(); i++) + result_sum += result[i]; + if (result_sum != sum_) + __testlib_fail("random_t::partition: partition sum is expected to be the given sum"); + + if (*std::min_element(result.begin(), result.end()) < min_part) + __testlib_fail("random_t::partition: partition min is expected to be no less than the given min_part"); + + if (int(result.size()) != size || result.size() != (size_t) size) + __testlib_fail("random_t::partition: partition size is expected to be equal to the given size"); + + return result; + } + + /* Returns random (unsorted) partition which is a representation of sum as a sum of positive integers. */ + template + std::vector partition(int size, T sum) { + return partition(size, sum, T(1)); } }; @@ -908,21 +1330,18 @@ const unsigned long long random_t::mask = (1LL << 48) - 1; int random_t::version = -1; /* Pattern implementation */ -bool pattern::matches(const std::string& s) const -{ +bool pattern::matches(const std::string &s) const { return matches(s, 0); } -static bool __pattern_isSlash(const std::string& s, size_t pos) -{ +static bool __pattern_isSlash(const std::string &s, size_t pos) { return s[pos] == '\\'; } #ifdef __GNUC__ __attribute__((pure)) #endif -static bool __pattern_isCommandChar(const std::string& s, size_t pos, char value) -{ +static bool __pattern_isCommandChar(const std::string &s, size_t pos, char value) { if (pos >= s.length()) return false; @@ -935,8 +1354,7 @@ static bool __pattern_isCommandChar(const std::string& s, size_t pos, char value return slashes % 2 == 0 && s[pos] == value; } -static char __pattern_getChar(const std::string& s, size_t& pos) -{ +static char __pattern_getChar(const std::string &s, size_t &pos) { if (__pattern_isSlash(s, pos)) pos += 2; else @@ -948,12 +1366,10 @@ static char __pattern_getChar(const std::string& s, size_t& pos) #ifdef __GNUC__ __attribute__((pure)) #endif -static int __pattern_greedyMatch(const std::string& s, size_t pos, const std::vector chars) -{ +static int __pattern_greedyMatch(const std::string &s, size_t pos, const std::vector chars) { int result = 0; - while (pos < s.length()) - { + while (pos < s.length()) { char c = s[pos++]; if (!std::binary_search(chars.begin(), chars.end(), c)) break; @@ -964,17 +1380,14 @@ static int __pattern_greedyMatch(const std::string& s, size_t pos, const std::ve return result; } -std::string pattern::src() const -{ +std::string pattern::src() const { return s; } -bool pattern::matches(const std::string& s, size_t pos) const -{ +bool pattern::matches(const std::string &s, size_t pos) const { std::string result; - if (to > 0) - { + if (to > 0) { int size = __pattern_greedyMatch(s, pos, chars); if (size < from) return false; @@ -983,34 +1396,29 @@ bool pattern::matches(const std::string& s, size_t pos) const pos += size; } - if (children.size() > 0) - { + if (children.size() > 0) { for (size_t child = 0; child < children.size(); child++) if (children[child].matches(s, pos)) return true; return false; - } - else + } else return pos == s.length(); } -std::string pattern::next(random_t& rnd) const -{ +std::string pattern::next(random_t &rnd) const { std::string result; result.reserve(20); if (to == INT_MAX) __testlib_fail("pattern::next(random_t& rnd): can't process character '*' for generation"); - if (to > 0) - { + if (to > 0) { int count = rnd.next(to - from + 1) + from; for (int i = 0; i < count; i++) result += chars[rnd.next(int(chars.size()))]; } - if (children.size() > 0) - { + if (children.size() > 0) { int child = rnd.next(int(children.size())); result += children[child].next(rnd); } @@ -1018,23 +1426,19 @@ std::string pattern::next(random_t& rnd) const return result; } -static void __pattern_scanCounts(const std::string& s, size_t& pos, int& from, int& to) -{ - if (pos >= s.length()) - { +static void __pattern_scanCounts(const std::string &s, size_t &pos, int &from, int &to) { + if (pos >= s.length()) { from = to = 1; return; } - - if (__pattern_isCommandChar(s, pos, '{')) - { + + if (__pattern_isCommandChar(s, pos, '{')) { std::vector parts; std::string part; pos++; - while (pos < s.length() && !__pattern_isCommandChar(s, pos, '}')) - { + while (pos < s.length() && !__pattern_isCommandChar(s, pos, '}')) { if (__pattern_isCommandChar(s, pos, ',')) parts.push_back(part), part = "", pos++; else @@ -1054,12 +1458,15 @@ static void __pattern_scanCounts(const std::string& s, size_t& pos, int& from, i std::vector numbers; - for (size_t i = 0; i < parts.size(); i++) - { + for (size_t i = 0; i < parts.size(); i++) { if (parts[i].length() == 0) __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); int number; +#ifdef _MSC_VER + if (sscanf_s(parts[i].c_str(), "%d", &number) != 1) +#else if (std::sscanf(parts[i].c_str(), "%d", &number) != 1) +#endif __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); numbers.push_back(number); } @@ -1071,53 +1478,45 @@ static void __pattern_scanCounts(const std::string& s, size_t& pos, int& from, i if (from > to) __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); - } - else - { - if (__pattern_isCommandChar(s, pos, '?')) - { + } else { + if (__pattern_isCommandChar(s, pos, '?')) { from = 0, to = 1, pos++; return; } - if (__pattern_isCommandChar(s, pos, '*')) - { + if (__pattern_isCommandChar(s, pos, '*')) { from = 0, to = INT_MAX, pos++; return; } - if (__pattern_isCommandChar(s, pos, '+')) - { + if (__pattern_isCommandChar(s, pos, '+')) { from = 1, to = INT_MAX, pos++; return; } - + from = to = 1; } } -static std::vector __pattern_scanCharSet(const std::string& s, size_t& pos) -{ +static std::vector __pattern_scanCharSet(const std::string &s, size_t &pos) { if (pos >= s.length()) __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); std::vector result; - if (__pattern_isCommandChar(s, pos, '[')) - { + if (__pattern_isCommandChar(s, pos, '[')) { pos++; bool negative = __pattern_isCommandChar(s, pos, '^'); + if (negative) + pos++; char prev = 0; - while (pos < s.length() && !__pattern_isCommandChar(s, pos, ']')) - { - if (__pattern_isCommandChar(s, pos, '-') && prev != 0) - { + while (pos < s.length() && !__pattern_isCommandChar(s, pos, ']')) { + if (__pattern_isCommandChar(s, pos, '-') && prev != 0) { pos++; - if (pos + 1 == s.length() || __pattern_isCommandChar(s, pos, ']')) - { + if (pos + 1 == s.length() || __pattern_isCommandChar(s, pos, ']')) { result.push_back(prev); prev = '-'; continue; @@ -1132,9 +1531,7 @@ static std::vector __pattern_scanCharSet(const std::string& s, size_t& pos result.push_back(next); prev = 0; - } - else - { + } else { if (prev != 0) result.push_back(prev); prev = __pattern_getChar(s, pos); @@ -1149,12 +1546,10 @@ static std::vector __pattern_scanCharSet(const std::string& s, size_t& pos pos++; - if (negative) - { + if (negative) { std::sort(result.begin(), result.end()); std::vector actuals; - for (int code = 0; code < 255; code++) - { + for (int code = 0; code < 255; code++) { char c = char(code); if (!std::binary_search(result.begin(), result.end(), c)) actuals.push_back(c); @@ -1163,15 +1558,13 @@ static std::vector __pattern_scanCharSet(const std::string& s, size_t& pos } std::sort(result.begin(), result.end()); - } - else + } else result.push_back(__pattern_getChar(s, pos)); return result; } -pattern::pattern(std::string s): s(s), from(0), to(0) -{ +pattern::pattern(std::string s) : s(s), from(0), to(0) { std::string t; for (size_t i = 0; i < s.length(); i++) if (!__pattern_isCommandChar(s, i, ' ')) @@ -1182,22 +1575,19 @@ pattern::pattern(std::string s): s(s), from(0), to(0) int firstClose = -1; std::vector seps; - for (size_t i = 0; i < s.length(); i++) - { - if (__pattern_isCommandChar(s, i, '(')) - { + for (size_t i = 0; i < s.length(); i++) { + if (__pattern_isCommandChar(s, i, '(')) { opened++; continue; } - if (__pattern_isCommandChar(s, i, ')')) - { + if (__pattern_isCommandChar(s, i, ')')) { opened--; if (opened == 0 && firstClose == -1) firstClose = int(i); continue; } - + if (opened < 0) __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); @@ -1208,26 +1598,19 @@ pattern::pattern(std::string s): s(s), from(0), to(0) if (opened != 0) __testlib_fail("pattern: Illegal pattern (or part) \"" + s + "\""); - if (seps.size() == 0 && firstClose + 1 == (int)s.length() - && __pattern_isCommandChar(s, 0, '(') && __pattern_isCommandChar(s, s.length() - 1, ')')) - { + if (seps.size() == 0 && firstClose + 1 == (int) s.length() + && __pattern_isCommandChar(s, 0, '(') && __pattern_isCommandChar(s, s.length() - 1, ')')) { children.push_back(pattern(s.substr(1, s.length() - 2))); - } - else - { - if (seps.size() > 0) - { - seps.push_back(s.length()); + } else { + if (seps.size() > 0) { + seps.push_back(int(s.length())); int last = 0; - for (size_t i = 0; i < seps.size(); i++) - { + for (size_t i = 0; i < seps.size(); i++) { children.push_back(pattern(s.substr(last, seps[i] - last))); last = seps[i] + 1; } - } - else - { + } else { size_t pos = 0; chars = __pattern_scanCharSet(s, pos); __pattern_scanCounts(s, pos, from, to); @@ -1236,34 +1619,49 @@ pattern::pattern(std::string s): s(s), from(0), to(0) } } } + /* End of pattern implementation */ -template -inline bool isEof(C c) -{ - return (c == EOF || c == EOFC); +template +inline bool isEof(C c) { + return c == EOFC; } -template -inline bool isEoln(C c) -{ +template +inline bool isEoln(C c) { return (c == LF || c == CR); } template -inline bool isBlanks(C c) -{ +inline bool isBlanks(C c) { return (c == LF || c == CR || c == SPACE || c == TAB); } -enum TMode -{ +inline std::string trim(const std::string &s) { + if (s.empty()) + return s; + + int left = 0; + while (left < int(s.length()) && isBlanks(s[left])) + left++; + if (left >= int(s.length())) + return ""; + + int right = int(s.length()) - 1; + while (right >= 0 && isBlanks(s[right])) + right--; + if (right < 0) + return ""; + + return s.substr(left, right - left + 1); +} + +enum TMode { _input, _output, _answer }; /* Outcomes 6-15 are reserved for future use. */ -enum TResult -{ +enum TResult { _ok = 0, _wa = 1, _pe = 2, @@ -1274,201 +1672,271 @@ enum TResult _partially = 16 }; -enum TTestlibMode -{ - _unknown, _checker, _validator, _generator, _interactor +enum TTestlibMode { + _unknown, _checker, _validator, _generator, _interactor, _scorer }; #define _pc(exitCode) (TResult(_partially + (exitCode))) /* Outcomes 6-15 are reserved for future use. */ const std::string outcomes[] = { - "accepted", - "wrong-answer", - "presentation-error", - "fail", - "fail", + "accepted", + "wrong-answer", + "presentation-error", + "fail", + "fail", #ifndef PCMS2 - "points", + "points", #else - "relative-scoring", -#endif - "reserved", - "reserved", - "unexpected-eof", - "reserved", - "reserved", - "reserved", - "reserved", - "reserved", - "reserved", - "reserved", - "partially-correct" + "relative-scoring", +#endif + "reserved", + "reserved", + "unexpected-eof", + "reserved", + "reserved", + "reserved", + "reserved", + "reserved", + "reserved", + "reserved", + "partially-correct" }; -class InputStreamReader -{ +class InputStreamReader { public: - virtual int curChar() = 0; - virtual int nextChar() = 0; + virtual void setTestCase(int testCase) = 0; + + virtual std::vector getReadChars() = 0; + + virtual int curChar() = 0; + + virtual int nextChar() = 0; + virtual void skipChar() = 0; + virtual void unreadChar(int c) = 0; + virtual std::string getName() = 0; + virtual bool eof() = 0; + + virtual void close() = 0; + + virtual int getLine() = 0; + virtual ~InputStreamReader() = 0; }; -InputStreamReader::~InputStreamReader() -{ +InputStreamReader::~InputStreamReader() { // No operations. } -class StringInputStreamReader: public InputStreamReader -{ +class StringInputStreamReader : public InputStreamReader { private: std::string s; size_t pos; public: - StringInputStreamReader(const std::string& content): s(content), pos(0) - { + StringInputStreamReader(const std::string &content) : s(content), pos(0) { // No operations. } - int curChar() - { + void setTestCase(int) { + __testlib_fail("setTestCase not implemented in StringInputStreamReader"); + } + + std::vector getReadChars() { + __testlib_fail("getReadChars not implemented in StringInputStreamReader"); + } + + int curChar() { if (pos >= s.length()) - return EOF; + return EOFC; else - { return s[pos]; - } } - int nextChar() - { - if (pos >= s.length()) - return EOF; - else + int nextChar() { + if (pos >= s.length()) { + pos++; + return EOFC; + } else return s[pos++]; } - void skipChar() - { + void skipChar() { pos++; } - void unreadChar(int c) - { + void unreadChar(int c) { if (pos == 0) - __testlib_fail("FileFileInputStreamReader::unreadChar(int): pos == 0."); + __testlib_fail("StringInputStreamReader::unreadChar(int): pos == 0."); pos--; if (pos < s.length()) s[pos] = char(c); } - std::string getName() - { + std::string getName() { return __testlib_part(s); } - bool eof() - { + int getLine() { + return -1; + } + + bool eof() { return pos >= s.length(); } + + void close() { + // No operations. + } }; -class FileInputStreamReader: public InputStreamReader -{ +class FileInputStreamReader : public InputStreamReader { private: - std::FILE* file; + std::FILE *file; std::string name; + int line; + std::vector undoChars; + std::vector readChars; + std::vector undoReadChars; + + inline int postprocessGetc(int getcResult) { + if (getcResult != EOF) + return getcResult; + else + return EOFC; + } + + int getc(FILE *file) { + int c; + int rc; + + if (undoChars.empty()) { + c = rc = ::getc(file); + } else { + c = undoChars.back(); + undoChars.pop_back(); + rc = undoReadChars.back(); + undoReadChars.pop_back(); + } + + if (c == LF) + line++; + + readChars.push_back(rc); + return c; + } + + int ungetc(int c/*, FILE* file*/) { + if (!readChars.empty()) { + undoReadChars.push_back(readChars.back()); + readChars.pop_back(); + } + if (c == LF) + line--; + undoChars.push_back(c); + return c; + } public: - FileInputStreamReader(std::FILE* file, const std::string& name): file(file), name(name) - { + FileInputStreamReader(std::FILE *file, const std::string &name) : file(file), name(name), line(1) { // No operations. } - int curChar() - { + void setTestCase(int testCase) { + if (testCase < 0 || testCase > __TESTLIB_MAX_TEST_CASE) + __testlib_fail(testlib_format_("testCase expected fit in [1,%d], but %d doesn't", __TESTLIB_MAX_TEST_CASE, testCase)); + readChars.push_back(testCase + 256); + } + + std::vector getReadChars() { + return readChars; + } + + int curChar() { if (feof(file)) - return EOF; - else - { + return EOFC; + else { int c = getc(file); - ungetc(c, file); - return c; + ungetc(c/*, file*/); + return postprocessGetc(c); } } - int nextChar() - { + int nextChar() { if (feof(file)) - return EOF; + return EOFC; else - return getc(file); + return postprocessGetc(getc(file)); } - void skipChar() - { + void skipChar() { getc(file); } - void unreadChar(int c) - { - ungetc(c, file); + void unreadChar(int c) { + ungetc(c/*, file*/); } - std::string getName() - { + std::string getName() { return name; } - bool eof() - { - if (feof(file)) + int getLine() { + return line; + } + + bool eof() { + if (NULL == file || feof(file)) return true; - else - { - int c = getc(file); - bool result = (c == EOF); - ungetc(c, file); - return result; + else { + int c = nextChar(); + if (c == EOFC || (c == EOF && feof(file))) + return true; + unreadChar(c); + return false; + } + } + + void close() { + if (NULL != file) { + fclose(file); + file = NULL; } } }; -class BufferedFileInputStreamReader: public InputStreamReader -{ +class BufferedFileInputStreamReader : public InputStreamReader { private: static const size_t BUFFER_SIZE; - static const size_t MAX_UNREAD_COUNT; - - std::FILE* file; - char* buffer; - bool* isEof; - int bufferPos; - size_t bufferSize; + static const size_t MAX_UNREAD_COUNT; + std::FILE *file; std::string name; + int line; - bool refill() - { + char *buffer; + bool *isEof; + int bufferPos; + size_t bufferSize; + + bool refill() { if (NULL == file) __testlib_fail("BufferedFileInputStreamReader: file == NULL (" + getName() + ")"); - if (bufferPos >= int(bufferSize)) - { + if (bufferPos >= int(bufferSize)) { size_t readSize = fread( - buffer + MAX_UNREAD_COUNT, - 1, - BUFFER_SIZE - MAX_UNREAD_COUNT, - file + buffer + MAX_UNREAD_COUNT, + 1, + BUFFER_SIZE - MAX_UNREAD_COUNT, + file ); if (readSize < BUFFER_SIZE - MAX_UNREAD_COUNT - && ferror(file)) + && ferror(file)) __testlib_fail("BufferedFileInputStreamReader: unable to read (" + getName() + ")"); bufferSize = MAX_UNREAD_COUNT + readSize; @@ -1476,95 +1944,112 @@ class BufferedFileInputStreamReader: public InputStreamReader std::memset(isEof + MAX_UNREAD_COUNT, 0, sizeof(isEof[0]) * readSize); return readSize > 0; - } - else + } else return true; } + char increment() { + char c; + if ((c = buffer[bufferPos++]) == LF) + line++; + return c; + } + public: - BufferedFileInputStreamReader(std::FILE* file, const std::string& name): file(file), name(name) - { + BufferedFileInputStreamReader(std::FILE *file, const std::string &name) : file(file), name(name), line(1) { buffer = new char[BUFFER_SIZE]; isEof = new bool[BUFFER_SIZE]; bufferSize = MAX_UNREAD_COUNT; bufferPos = int(MAX_UNREAD_COUNT); } - ~BufferedFileInputStreamReader() - { - if (NULL != buffer) - { + ~BufferedFileInputStreamReader() { + if (NULL != buffer) { delete[] buffer; buffer = NULL; } - if (NULL != isEof) - { + if (NULL != isEof) { delete[] isEof; isEof = NULL; } } - int curChar() - { + void setTestCase(int) { + __testlib_fail("setTestCase not implemented in BufferedFileInputStreamReader"); + } + + std::vector getReadChars() { + __testlib_fail("getReadChars not implemented in BufferedFileInputStreamReader"); + } + + int curChar() { if (!refill()) - return EOF; + return EOFC; - return isEof[bufferPos] ? EOF : buffer[bufferPos]; + return isEof[bufferPos] ? EOFC : buffer[bufferPos]; } - int nextChar() - { + int nextChar() { if (!refill()) - return EOF; + return EOFC; - return isEof[bufferPos] ? EOF : buffer[bufferPos++]; + return isEof[bufferPos] ? EOFC : increment(); } - void skipChar() - { - bufferPos++; + void skipChar() { + increment(); } - void unreadChar(int c) - { + void unreadChar(int c) { bufferPos--; if (bufferPos < 0) __testlib_fail("BufferedFileInputStreamReader::unreadChar(int): bufferPos < 0"); - isEof[bufferPos] = (c == EOF); - buffer[bufferPos] = char(c == EOF ? EOFC : c); + isEof[bufferPos] = (c == EOFC); + buffer[bufferPos] = char(c); + if (c == LF) + line--; } - std::string getName() - { + std::string getName() { return name; } - - bool eof() - { - return !refill() || EOF == curChar(); + + int getLine() { + return line; + } + + bool eof() { + return !refill() || EOFC == curChar(); + } + + void close() { + if (NULL != file) { + fclose(file); + file = NULL; + } } }; -const size_t BufferedFileInputStreamReader::BUFFER_SIZE = 1000000; -const size_t BufferedFileInputStreamReader::MAX_UNREAD_COUNT = 100; +const size_t BufferedFileInputStreamReader::BUFFER_SIZE = 2000000; +const size_t BufferedFileInputStreamReader::MAX_UNREAD_COUNT = BufferedFileInputStreamReader::BUFFER_SIZE / 2; /* * Streams to be used for reading data in checkers or validators. * Each read*() method moves pointer to the next character after the * read value. */ -struct InStream -{ +struct InStream { /* Do not use them. */ InStream(); + ~InStream(); /* Wrap std::string with InStream. */ - InStream(const InStream& baseStream, std::string content); + InStream(const InStream &baseStream, std::string content); - InputStreamReader* reader; + InputStreamReader *reader; + int lastLine; - std::FILE* file; std::string name; TMode mode; bool opened; @@ -1574,237 +2059,805 @@ struct InStream int wordReserveSize; std::string _tmpReadToken; + int readManyIteration; + size_t maxFileSize; + size_t maxTokenLength; + size_t maxMessageLength; + void init(std::string fileName, TMode mode); - void init(std::FILE* f, TMode mode); - /* Moves stream pointer to the first non-white-space character or EOF. */ + void init(std::FILE *f, TMode mode); + + void setTestCase(int testCase); + std::vector getReadChars(); + + /* Moves stream pointer to the first non-white-space character or EOF. */ void skipBlanks(); - + /* Returns current character in the stream. Doesn't remove it from stream. */ char curChar(); + /* Moves stream pointer one character forward. */ void skipChar(); + /* Returns current character and moves pointer one character forward. */ char nextChar(); - + /* Returns current character and moves pointer one character forward. */ char readChar(); + /* As "readChar()" but ensures that the result is equal to given parameter. */ char readChar(char c); + /* As "readChar()" but ensures that the result is equal to the space (code=32). */ char readSpace(); + /* Puts back the character into the stream. */ void unreadChar(char c); /* Reopens stream, you should not use it. */ - void reset(); + void reset(std::FILE *file = NULL); + /* Checks that current position is EOF. If not it doesn't move stream pointer. */ bool eof(); + /* Moves pointer to the first non-white-space character and calls "eof()". */ bool seekEof(); - /* - * Checks that current position contains EOLN. - * If not it doesn't move stream pointer. + /* + * Checks that current position contains EOLN. + * If not it doesn't move stream pointer. * In strict mode expects "#13#10" for windows or "#10" for other platforms. */ bool eoln(); + /* Moves pointer to the first non-space and non-tab character and calls "eoln()". */ bool seekEoln(); /* Moves stream pointer to the first character of the next line (if exists). */ void nextLine(); - /* - * Reads new token. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + /* + * Reads new token. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ std::string readWord(); - /* The same as "readWord()", it is preffered to use "readToken()". */ + + /* The same as "readWord()", it is preferred to use "readToken()". */ std::string readToken(); + /* The same as "readWord()", but ensures that token matches to given pattern. */ - std::string readWord(const std::string& ptrn, const std::string& variableName = ""); - std::string readWord(const pattern& p, const std::string& variableName = ""); + std::string readWord(const std::string &ptrn, const std::string &variableName = ""); + + std::string readWord(const pattern &p, const std::string &variableName = ""); + + std::vector + readWords(int size, const std::string &ptrn, const std::string &variablesName = "", int indexBase = 1); + + std::vector + readWords(int size, const pattern &p, const std::string &variablesName = "", int indexBase = 1); + + std::vector readWords(int size, int indexBase = 1); + /* The same as "readToken()", but ensures that token matches to given pattern. */ - std::string readToken(const std::string& ptrn, const std::string& variableName = ""); - std::string readToken(const pattern& p, const std::string& variableName = ""); + std::string readToken(const std::string &ptrn, const std::string &variableName = ""); + + std::string readToken(const pattern &p, const std::string &variableName = ""); + + std::vector + readTokens(int size, const std::string &ptrn, const std::string &variablesName = "", int indexBase = 1); + + std::vector + readTokens(int size, const pattern &p, const std::string &variablesName = "", int indexBase = 1); + + std::vector readTokens(int size, int indexBase = 1); - void readWordTo(std::string& result); - void readWordTo(std::string& result, const pattern& p, const std::string& variableName = ""); - void readWordTo(std::string& result, const std::string& ptrn, const std::string& variableName = ""); + void readWordTo(std::string &result); - void readTokenTo(std::string& result); - void readTokenTo(std::string& result, const pattern& p, const std::string& variableName = ""); - void readTokenTo(std::string& result, const std::string& ptrn, const std::string& variableName = ""); + void readWordTo(std::string &result, const pattern &p, const std::string &variableName = ""); - /* - * Reads new long long value. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + void readWordTo(std::string &result, const std::string &ptrn, const std::string &variableName = ""); + + void readTokenTo(std::string &result); + + void readTokenTo(std::string &result, const pattern &p, const std::string &variableName = ""); + + void readTokenTo(std::string &result, const std::string &ptrn, const std::string &variableName = ""); + + /* + * Reads new long long value. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ long long readLong(); - /* - * Reads new int. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + + unsigned long long readUnsignedLong(); + + /* + * Reads new int. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ int readInteger(); - /* - * Reads new int. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + + /* + * Reads new int. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ int readInt(); /* As "readLong()" but ensures that value in the range [minv,maxv]. */ - long long readLong(long long minv, long long maxv, const std::string& variableName = ""); + long long readLong(long long minv, long long maxv, const std::string &variableName = ""); + + /* Reads space-separated sequence of long longs. */ + std::vector + readLongs(int size, long long minv, long long maxv, const std::string &variablesName = "", int indexBase = 1); + + /* Reads space-separated sequence of long longs. */ + std::vector readLongs(int size, int indexBase = 1); + + unsigned long long + readUnsignedLong(unsigned long long minv, unsigned long long maxv, const std::string &variableName = ""); + + std::vector + readUnsignedLongs(int size, unsigned long long minv, unsigned long long maxv, const std::string &variablesName = "", + int indexBase = 1); + + std::vector readUnsignedLongs(int size, int indexBase = 1); + + unsigned long long readLong(unsigned long long minv, unsigned long long maxv, const std::string &variableName = ""); + + std::vector + readLongs(int size, unsigned long long minv, unsigned long long maxv, const std::string &variablesName = "", + int indexBase = 1); + /* As "readInteger()" but ensures that value in the range [minv,maxv]. */ - int readInteger(int minv, int maxv, const std::string& variableName = ""); + int readInteger(int minv, int maxv, const std::string &variableName = ""); + /* As "readInt()" but ensures that value in the range [minv,maxv]. */ - - int readInt(int minv, int maxv, const std::string& variableName = ""); - /* - * Reads new double. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + int readInt(int minv, int maxv, const std::string &variableName = ""); + + /* Reads space-separated sequence of integers. */ + std::vector + readIntegers(int size, int minv, int maxv, const std::string &variablesName = "", int indexBase = 1); + + /* Reads space-separated sequence of integers. */ + std::vector readIntegers(int size, int indexBase = 1); + + /* Reads space-separated sequence of integers. */ + std::vector readInts(int size, int minv, int maxv, const std::string &variablesName = "", int indexBase = 1); + + /* Reads space-separated sequence of integers. */ + std::vector readInts(int size, int indexBase = 1); + + /* + * Reads new double. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ double readReal(); - /* - * Reads new double. Ignores white-spaces into the non-strict mode - * (strict mode is used in validators usually). + + /* + * Reads new double. Ignores white-spaces into the non-strict mode + * (strict mode is used in validators usually). */ double readDouble(); - + /* As "readReal()" but ensures that value in the range [minv,maxv]. */ - double readReal(double minv, double maxv, const std::string& variableName = ""); + double readReal(double minv, double maxv, const std::string &variableName = ""); + + std::vector + readReals(int size, double minv, double maxv, const std::string &variablesName = "", int indexBase = 1); + + std::vector readReals(int size, int indexBase = 1); + /* As "readDouble()" but ensures that value in the range [minv,maxv]. */ - double readDouble(double minv, double maxv, const std::string& variableName = ""); - - /* + double readDouble(double minv, double maxv, const std::string &variableName = ""); + + std::vector + readDoubles(int size, double minv, double maxv, const std::string &variablesName = "", int indexBase = 1); + + std::vector readDoubles(int size, int indexBase = 1); + + /* * As "readReal()" but ensures that value in the range [minv,maxv] and * number of digit after the decimal point is in range [minAfterPointDigitCount,maxAfterPointDigitCount] * and number is in the form "[-]digit(s)[.digit(s)]". */ double readStrictReal(double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variableName = ""); - /* + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variableName = ""); + + std::vector readStrictReals(int size, double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variablesName = "", int indexBase = 1); + + /* * As "readDouble()" but ensures that value in the range [minv,maxv] and * number of digit after the decimal point is in range [minAfterPointDigitCount,maxAfterPointDigitCount] * and number is in the form "[-]digit(s)[.digit(s)]". */ double readStrictDouble(double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variableName = ""); - + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variableName = ""); + + std::vector readStrictDoubles(int size, double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variablesName = "", int indexBase = 1); + /* As readLine(). */ std::string readString(); + + /* Read many lines. */ + std::vector readStrings(int size, int indexBase = 1); + /* See readLine(). */ - void readStringTo(std::string& result); + void readStringTo(std::string &result); + /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */ - std::string readString(const pattern& p, const std::string& variableName = ""); + std::string readString(const pattern &p, const std::string &variableName = ""); + /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */ - std::string readString(const std::string& ptrn, const std::string& variableName = ""); + std::string readString(const std::string &ptrn, const std::string &variableName = ""); + + /* Read many lines. */ + std::vector + readStrings(int size, const pattern &p, const std::string &variableName = "", int indexBase = 1); + + /* Read many lines. */ + std::vector + readStrings(int size, const std::string &ptrn, const std::string &variableName = "", int indexBase = 1); + /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */ - void readStringTo(std::string& result, const pattern& p, const std::string& variableName = ""); + void readStringTo(std::string &result, const pattern &p, const std::string &variableName = ""); + /* The same as "readLine()/readString()", but ensures that line matches to the given pattern. */ - void readStringTo(std::string& result, const std::string& ptrn, const std::string& variableName = ""); + void readStringTo(std::string &result, const std::string &ptrn, const std::string &variableName = ""); - /* - * Reads line from the current position to EOLN or EOF. Moves stream pointer to - * the first character of the new line (if possible). + /* + * Reads line from the current position to EOLN or EOF. Moves stream pointer to + * the first character of the new line (if possible). */ std::string readLine(); + + /* Read many lines. */ + std::vector readLines(int size, int indexBase = 1); + /* See readLine(). */ - void readLineTo(std::string& result); + void readLineTo(std::string &result); + /* The same as "readLine()", but ensures that line matches to the given pattern. */ - std::string readLine(const pattern& p, const std::string& variableName = ""); + std::string readLine(const pattern &p, const std::string &variableName = ""); + /* The same as "readLine()", but ensures that line matches to the given pattern. */ - std::string readLine(const std::string& ptrn, const std::string& variableName = ""); + std::string readLine(const std::string &ptrn, const std::string &variableName = ""); + + /* Read many lines. */ + std::vector + readLines(int size, const pattern &p, const std::string &variableName = "", int indexBase = 1); + + /* Read many lines. */ + std::vector + readLines(int size, const std::string &ptrn, const std::string &variableName = "", int indexBase = 1); + /* The same as "readLine()", but ensures that line matches to the given pattern. */ - void readLineTo(std::string& result, const pattern& p, const std::string& variableName = ""); + void readLineTo(std::string &result, const pattern &p, const std::string &variableName = ""); + /* The same as "readLine()", but ensures that line matches to the given pattern. */ - void readLineTo(std::string& result, const std::string& ptrn, const std::string& variableName = ""); + void readLineTo(std::string &result, const std::string &ptrn, const std::string &variableName = ""); /* Reads EOLN or fails. Use it in validators. Calls "eoln()" method internally. */ void readEoln(); + /* Reads EOF or fails. Use it in validators. Calls "eof()" method internally. */ void readEof(); - /* + /* + * Quit-functions aborts program with and : + * input/answer streams replace any result to FAIL. + */ + NORETURN void quit(TResult result, const char *msg); + /* * Quit-functions aborts program with and : * input/answer streams replace any result to FAIL. */ - NORETURN void quit(TResult result, const char* msg); - /* + NORETURN void quitf(TResult result, const char *msg, ...); + + /* * Quit-functions aborts program with and : * input/answer streams replace any result to FAIL. */ - NORETURN void quitf(TResult result, const char* msg, ...); - /* + void quitif(bool condition, TResult result, const char *msg, ...); + /* * Quit-functions aborts program with and : * input/answer streams replace any result to FAIL. */ NORETURN void quits(TResult result, std::string msg); + /* + * Checks condition and aborts a program if condition is false. + * Returns _wa for ouf and _fail on any other streams. + */ +#ifdef __GNUC__ + __attribute__ ((format (printf, 3, 4))) +#endif + void ensuref(bool cond, const char *format, ...); + + void __testlib_ensure(bool cond, std::string message); + void close(); - const static WORD LightGray = 0x07; - const static WORD LightRed = 0x0c; - const static WORD LightCyan = 0x0b; - const static WORD LightGreen = 0x0a; - const static WORD LightYellow = 0x0e; + const static int NO_INDEX = INT_MAX; + const static char OPEN_BRACKET = char(11); + const static char CLOSE_BRACKET = char(17); + + const static WORD LightGray = 0x07; + const static WORD LightRed = 0x0c; + const static WORD LightCyan = 0x0b; + const static WORD LightGreen = 0x0a; + const static WORD LightYellow = 0x0e; static void textColor(WORD color); - static void quitscr(WORD color, const char* msg); + + static void quitscr(WORD color, const char *msg); + static void quitscrS(WORD color, std::string msg); - void xmlSafeWrite(std::FILE * file, const char* msg); + + void xmlSafeWrite(std::FILE *file, const char *msg); + + /* Skips UTF-8 Byte Order Mark. */ + void skipBom(); private: - InStream(const InStream&); - InStream& operator =(const InStream&); + InStream(const InStream &); + + InStream &operator=(const InStream &); }; InStream inf; InStream ouf; InStream ans; bool appesMode; +std::string appesModeEncoding = "windows-1251"; std::string resultName; std::string checkerName = "untitled checker"; random_t rnd; TTestlibMode testlibMode = _unknown; double __testlib_points = std::numeric_limits::infinity(); -struct TestlibFinalizeGuard -{ +const size_t VALIDATOR_MAX_VARIABLE_COUNT = 255; + +struct ValidatorBoundsHit { + static const double EPS; + bool minHit; + bool maxHit; + + ValidatorBoundsHit(bool minHit = false, bool maxHit = false) : minHit(minHit), maxHit(maxHit) { + }; + + ValidatorBoundsHit merge(const ValidatorBoundsHit &validatorBoundsHit, bool ignoreMinBound, bool ignoreMaxBound) { + return ValidatorBoundsHit( + __testlib_max(minHit, validatorBoundsHit.minHit) || ignoreMinBound, + __testlib_max(maxHit, validatorBoundsHit.maxHit) || ignoreMaxBound + ); + } +}; + +struct ConstantBound { + std::string value; + bool broken; + + template + void adjust(T t) { + std::string t_string = std::to_string(t); + if (t_string.length() >= 32) { + broken = true; + value = ""; + } else { + if (!broken && value.empty()) + value = t_string; + if (!broken && value != t_string) { + broken = true; + value = ""; + } + } + } + + bool has_value() { + return !value.empty() && !broken && value.length() < 32; + } +}; + +struct ConstantBounds { + ConstantBound lowerBound; + ConstantBound upperBound; +}; + +const double ValidatorBoundsHit::EPS = 1E-12; + +class Validator { +private: + const static std::string TEST_MARKUP_HEADER; + const static std::string TEST_CASE_OPEN_TAG; + const static std::string TEST_CASE_CLOSE_TAG; + + bool _initialized; + std::string _testset; + std::string _group; + + std::string _testOverviewLogFileName; + std::string _testMarkupFileName; + int _testCase = -1; + std::string _testCaseFileName; + + std::map _boundsHitByVariableName; + std::map _constantBoundsByVariableName; + std::set _features; + std::set _hitFeatures; + std::set _variables; + + bool isVariableNameBoundsAnalyzable(const std::string &variableName) { + for (size_t i = 0; i < variableName.length(); i++) + if ((variableName[i] >= '0' && variableName[i] <= '9') || variableName[i] < ' ') + return false; + return true; + } + + bool isFeatureNameAnalyzable(const std::string &featureName) { + for (size_t i = 0; i < featureName.length(); i++) + if (featureName[i] < ' ') + return false; + return true; + } + +public: + Validator() : _initialized(false), _testset("tests"), _group() { + } + + void initialize() { + _initialized = true; + } + + std::string testset() const { + if (!_initialized) + __testlib_fail("Validator should be initialized with registerValidation(argc, argv) instead of registerValidation() to support validator.testset()"); + return _testset; + } + + std::string group() const { + if (!_initialized) + __testlib_fail("Validator should be initialized with registerValidation(argc, argv) instead of registerValidation() to support validator.group()"); + return _group; + } + + std::string testOverviewLogFileName() const { + return _testOverviewLogFileName; + } + + std::string testMarkupFileName() const { + return _testMarkupFileName; + } + + int testCase() const { + return _testCase; + } + + std::string testCaseFileName() const { + return _testCaseFileName; + } + + void setTestset(const char *const testset) { + _testset = testset; + } + + void setGroup(const char *const group) { + _group = group; + } + + void setTestOverviewLogFileName(const char *const testOverviewLogFileName) { + _testOverviewLogFileName = testOverviewLogFileName; + } + + void setTestMarkupFileName(const char *const testMarkupFileName) { + _testMarkupFileName = testMarkupFileName; + } + + void setTestCase(int testCase) { + _testCase = testCase; + } + + void setTestCaseFileName(const char *const testCaseFileName) { + _testCaseFileName = testCaseFileName; + } + + std::string prepVariableName(const std::string &variableName) { + if (variableName.length() >= 2 && variableName != "~~") { + if (variableName[0] == '~' && variableName.back() != '~') + return variableName.substr(1); + if (variableName[0] != '~' && variableName.back() == '~') + return variableName.substr(0, variableName.length() - 1); + if (variableName[0] == '~' && variableName.back() == '~') + return variableName.substr(1, variableName.length() - 2); + } + return variableName; + } + + bool ignoreMinBound(const std::string &variableName) { + return variableName.length() >= 2 && variableName != "~~" && variableName[0] == '~'; + } + + bool ignoreMaxBound(const std::string &variableName) { + return variableName.length() >= 2 && variableName != "~~" && variableName.back() == '~'; + } + + void addBoundsHit(const std::string &variableName, ValidatorBoundsHit boundsHit) { + if (isVariableNameBoundsAnalyzable(variableName) + && _boundsHitByVariableName.size() < VALIDATOR_MAX_VARIABLE_COUNT) { + std::string preparedVariableName = prepVariableName(variableName); + _boundsHitByVariableName[preparedVariableName] = boundsHit.merge(_boundsHitByVariableName[preparedVariableName], + ignoreMinBound(variableName), ignoreMaxBound(variableName)); + } + } + + void addVariable(const std::string &variableName) { + if (isVariableNameBoundsAnalyzable(variableName) + && _variables.size() < VALIDATOR_MAX_VARIABLE_COUNT) { + std::string preparedVariableName = prepVariableName(variableName); + _variables.insert(preparedVariableName); + } + } + + std::string getVariablesLog() { + std::string result; + for (const std::string &variableName: _variables) + result += "variable \"" + variableName + "\"\n"; + return result; + } + + template + void adjustConstantBounds(const std::string &variableName, T lower, T upper) { + if (isVariableNameBoundsAnalyzable(variableName) + && _constantBoundsByVariableName.size() < VALIDATOR_MAX_VARIABLE_COUNT) { + std::string preparedVariableName = prepVariableName(variableName); + _constantBoundsByVariableName[preparedVariableName].lowerBound.adjust(lower); + _constantBoundsByVariableName[preparedVariableName].upperBound.adjust(upper); + } + } + + std::string getBoundsHitLog() { + std::string result; + for (std::map::iterator i = _boundsHitByVariableName.begin(); + i != _boundsHitByVariableName.end(); + i++) { + result += "\"" + i->first + "\":"; + if (i->second.minHit) + result += " min-value-hit"; + if (i->second.maxHit) + result += " max-value-hit"; + result += "\n"; + } + return result; + } + + std::string getConstantBoundsLog() { + std::string result; + for (std::map::iterator i = _constantBoundsByVariableName.begin(); + i != _constantBoundsByVariableName.end(); + i++) { + if (i->second.lowerBound.has_value() || i->second.upperBound.has_value()) { + result += "constant-bounds \"" + i->first + "\":"; + if (i->second.lowerBound.has_value()) + result += " " + i->second.lowerBound.value; + else + result += " ?"; + if (i->second.upperBound.has_value()) + result += " " + i->second.upperBound.value; + else + result += " ?"; + result += "\n"; + } + } + return result; + } + + std::string getFeaturesLog() { + std::string result; + for (std::set::iterator i = _features.begin(); + i != _features.end(); + i++) { + result += "feature \"" + *i + "\":"; + if (_hitFeatures.count(*i)) + result += " hit"; + result += "\n"; + } + return result; + } + + void writeTestOverviewLog() { + if (!_testOverviewLogFileName.empty()) { + std::string fileName(_testOverviewLogFileName); + _testOverviewLogFileName = ""; + + FILE* f; + bool standard_file = false; + if (fileName == "stdout") + f = stdout, standard_file = true; + else if (fileName == "stderr") + f = stderr, standard_file = true; + else { + f = testlib_fopen_(fileName.c_str(), "wb"); + if (NULL == f) + __testlib_fail("Validator::writeTestOverviewLog: can't write test overview log to (" + fileName + ")"); + } + fprintf(f, "%s%s%s%s", + getBoundsHitLog().c_str(), + getFeaturesLog().c_str(), + getConstantBoundsLog().c_str(), + getVariablesLog().c_str()); + std::fflush(f); + if (!standard_file) + if (std::fclose(f)) + __testlib_fail("Validator::writeTestOverviewLog: can't close test overview log file (" + fileName + ")"); + } + } + + void writeTestMarkup() { + if (!_testMarkupFileName.empty()) { + std::vector readChars = inf.getReadChars(); + if (!readChars.empty()) { + std::string markup(TEST_MARKUP_HEADER); + for (size_t i = 0; i < readChars.size(); i++) { + int c = readChars[i]; + if (i + 1 == readChars.size() && c == -1) + continue; + if (c <= 256) { + char cc = char(c); + if (cc == '\\' || cc == '!') + markup += '\\'; + markup += cc; + } else { + markup += TEST_CASE_OPEN_TAG; + markup += toString(c - 256); + markup += TEST_CASE_CLOSE_TAG; + } + } + FILE* f; + bool standard_file = false; + if (_testMarkupFileName == "stdout") + f = stdout, standard_file = true; + else if (_testMarkupFileName == "stderr") + f = stderr, standard_file = true; + else { + f = testlib_fopen_(_testMarkupFileName.c_str(), "wb"); + if (NULL == f) + __testlib_fail("Validator::writeTestMarkup: can't write test markup to (" + _testMarkupFileName + ")"); + } + std::fprintf(f, "%s", markup.c_str()); + std::fflush(f); + if (!standard_file) + if (std::fclose(f)) + __testlib_fail("Validator::writeTestMarkup: can't close test markup file (" + _testCaseFileName + ")"); + } + } + } + + void writeTestCase() { + if (_testCase > 0) { + std::vector readChars = inf.getReadChars(); + if (!readChars.empty()) { + std::string content, testCaseContent; + bool matchedTestCase = false; + for (size_t i = 0; i < readChars.size(); i++) { + int c = readChars[i]; + if (i + 1 == readChars.size() && c == -1) + continue; + if (c <= 256) + content += char(c); + else { + if (matchedTestCase) { + testCaseContent = content; + matchedTestCase = false; + } + content = ""; + int testCase = c - 256; + if (testCase == _testCase) + matchedTestCase = true; + } + } + if (matchedTestCase) + testCaseContent = content; + + if (!testCaseContent.empty()) { + FILE* f; + bool standard_file = false; + if (_testCaseFileName.empty() || _testCaseFileName == "stdout") + f = stdout, standard_file = true; + else if (_testCaseFileName == "stderr") + f = stderr, standard_file = true; + else { + f = testlib_fopen_(_testCaseFileName.c_str(), "wb"); + if (NULL == f) + __testlib_fail("Validator::writeTestCase: can't write test case to (" + _testCaseFileName + ")"); + } + std::fprintf(f, "%s", testCaseContent.c_str()); + std::fflush(f); + if (!standard_file) + if (std::fclose(f)) + __testlib_fail("Validator::writeTestCase: can't close test case file (" + _testCaseFileName + ")"); + } + } + } + } + + void addFeature(const std::string &feature) { + if (_features.count(feature)) + __testlib_fail("Feature " + feature + " registered twice."); + if (!isFeatureNameAnalyzable(feature)) + __testlib_fail("Feature name '" + feature + "' contains restricted characters."); + + _features.insert(feature); + } + + void feature(const std::string &feature) { + if (!isFeatureNameAnalyzable(feature)) + __testlib_fail("Feature name '" + feature + "' contains restricted characters."); + + if (!_features.count(feature)) + __testlib_fail("Feature " + feature + " didn't registered via addFeature(feature)."); + + _hitFeatures.insert(feature); + } +} validator; + +const std::string Validator::TEST_MARKUP_HEADER = "MU\xF3\x01"; +const std::string Validator::TEST_CASE_OPEN_TAG = "!c"; +const std::string Validator::TEST_CASE_CLOSE_TAG = ";"; + +struct TestlibFinalizeGuard { static bool alive; + static bool registered; + int quitCount, readEofCount; - TestlibFinalizeGuard() : quitCount(0), readEofCount(0) - { + TestlibFinalizeGuard() : quitCount(0), readEofCount(0) { // No operations. } - ~TestlibFinalizeGuard() - { + ~TestlibFinalizeGuard() { bool _alive = alive; alive = false; - if (_alive) - { + if (_alive) { if (testlibMode == _checker && quitCount == 0) __testlib_fail("Checker must end with quit or quitf call."); if (testlibMode == _validator && readEofCount == 0 && quitCount == 0) __testlib_fail("Validator must end with readEof call."); + + /* opts */ + autoEnsureNoUnusedOpts(); + + if (!registered) + __testlib_fail("Call register-function in the first line of the main (registerTestlibCmd or other similar)"); + } + + if (__testlib_exitCode == 0) { + validator.writeTestOverviewLog(); + validator.writeTestMarkup(); + validator.writeTestCase(); } } + +private: + /* opts */ + void autoEnsureNoUnusedOpts(); }; bool TestlibFinalizeGuard::alive = true; -TestlibFinalizeGuard testlibFinalizeGuard; +bool TestlibFinalizeGuard::registered = false; +extern TestlibFinalizeGuard testlibFinalizeGuard; /* * Call it to disable checks on finalization. */ -void disableFinalizeGuard() -{ +void disableFinalizeGuard() { TestlibFinalizeGuard::alive = false; } @@ -1815,57 +2868,79 @@ std::fstream tout; /* implementation */ -template -static std::string vtos(const T& t) -{ - std::string s; - std::stringstream ss; - ss << t; - ss >> s; - return s; -} - -template -static std::string toString(const T& t) -{ - return vtos(t); -} - -InStream::InStream() -{ - file = NULL; +InStream::InStream() { + reader = NULL; + lastLine = -1; + opened = false; name = ""; mode = _input; strict = false; stdfile = false; wordReserveSize = 4; + readManyIteration = NO_INDEX; + maxFileSize = 128 * 1024 * 1024; // 128MB. + maxTokenLength = 32 * 1024 * 1024; // 32MB. + maxMessageLength = 32000; } -InStream::InStream(const InStream& baseStream, std::string content) -{ - file = (FILE*)0xbadfeed; - stdfile = false; +InStream::InStream(const InStream &baseStream, std::string content) { reader = new StringInputStreamReader(content); + lastLine = -1; opened = true; strict = baseStream.strict; + stdfile = false; mode = baseStream.mode; name = "based on " + baseStream.name; + readManyIteration = NO_INDEX; + maxFileSize = 128 * 1024 * 1024; // 128MB. + maxTokenLength = 32 * 1024 * 1024; // 32MB. + maxMessageLength = 32000; } -InStream::~InStream() -{ - if (NULL != reader) - { +InStream::~InStream() { + if (NULL != reader) { + reader->close(); delete reader; reader = NULL; } } +void InStream::setTestCase(int testCase) { + if (testlibMode != _validator || mode != _input || !stdfile || this != &inf) + __testlib_fail("InStream::setTestCase can be used only for inf in validator-mode." + " Actually, prefer setTestCase function instead of InStream member"); + reader->setTestCase(testCase); +} + +std::vector InStream::getReadChars() { + if (testlibMode != _validator || mode != _input || !stdfile || this != &inf) + __testlib_fail("InStream::getReadChars can be used only for inf in validator-mode."); + return reader == NULL ? std::vector() : reader->getReadChars(); +} + +void setTestCase(int testCase) { + static bool first_run = true; + static bool zero_based = false; + + if (first_run && testCase == 0) + zero_based = true; + + if (zero_based) + testCase++; + + __testlib_hasTestCase = true; + __testlib_testCase = testCase; + + if (testlibMode == _validator) + inf.setTestCase(testCase); + + first_run = false; +} + #ifdef __GNUC__ __attribute__((const)) #endif -int resultExitCode(TResult r) -{ +int resultExitCode(TResult r) { if (r == _ok) return OK_EXIT_CODE; if (r == _wa) @@ -1889,69 +2964,173 @@ int resultExitCode(TResult r) return FAIL_EXIT_CODE; } -void InStream::textColor(WORD color) -{ -#if defined(ON_WINDOWS) && (!defined(_MSC_VER) || _MSC_VER>1400) +void InStream::textColor( +#if !(defined(ON_WINDOWS) && (!defined(_MSC_VER) || _MSC_VER > 1400)) && defined(__GNUC__) + __attribute__((unused)) +#endif + WORD color +) { +#if defined(ON_WINDOWS) && (!defined(_MSC_VER) || _MSC_VER > 1400) HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(handle, color); #endif - -#ifdef linux - char *shell_path = getenv("SHELL"); - if (shell_path && strcmp(shell_path, "/bin/bash") == 0 && isatty(2)) - { - switch (color) - { - case 0: - fprintf(stderr, "\e[0m"); - break; - case LightRed: - fprintf(stderr, "\e[0;31m"); - break; - case LightGreen: - fprintf(stderr, "\e[0;32m"); - break; - case LightYellow: - fprintf(stderr, "\e[0;33m"); - break; - case LightCyan: - fprintf(stderr, "\e[0;36m"); - break; - case LightGray: - fprintf(stderr, "\e[0m"); - break; - } - } -#endif -} - -NORETURN void halt(int exitCode) -{ -#ifdef FOOTER - InStream::textColor(InStream::LightGray); - std::fprintf(stderr, "Checker: \"%s\"\n", checkerName.c_str()); - std::fprintf(stderr, "Exit code: %d\n", exitCode); - InStream::textColor(InStream::LightGray); +#if !defined(ON_WINDOWS) && defined(__GNUC__) + if (isatty(2)) + { + switch (color) + { + case LightRed: + fprintf(stderr, "\033[1;31m"); + break; + case LightCyan: + fprintf(stderr, "\033[1;36m"); + break; + case LightGreen: + fprintf(stderr, "\033[1;32m"); + break; + case LightYellow: + fprintf(stderr, "\033[1;33m"); + break; + case LightGray: + default: + fprintf(stderr, "\033[0m"); + } + } #endif -#ifdef linux - InStream::textColor(0); +} + +#ifdef TESTLIB_THROW_EXIT_EXCEPTION_INSTEAD_OF_EXIT +class exit_exception: public std::exception { +private: + int exitCode; +public: + exit_exception(int exitCode): exitCode(exitCode) {} + int getExitCode() { return exitCode; } +}; +#endif + +NORETURN void halt(int exitCode) { +#ifdef FOOTER + InStream::textColor(InStream::LightGray); + std::fprintf(stderr, "Checker: \"%s\"\n", checkerName.c_str()); + std::fprintf(stderr, "Exit code: %d\n", exitCode); + InStream::textColor(InStream::LightGray); +#endif + __testlib_exitCode = exitCode; +#ifdef TESTLIB_THROW_EXIT_EXCEPTION_INSTEAD_OF_EXIT + throw exit_exception(exitCode); #endif std::exit(exitCode); } -NORETURN void InStream::quit(TResult result, const char* msg) -{ +static bool __testlib_shouldCheckDirt(TResult result) { + return result == _ok || result == _points || result >= _partially; +} + +static std::string __testlib_appendMessage(const std::string &message, const std::string &extra) { + int openPos = -1, closePos = -1; + for (size_t i = 0; i < message.length(); i++) { + if (message[i] == InStream::OPEN_BRACKET) { + if (openPos == -1) + openPos = int(i); + else + openPos = INT_MAX; + } + if (message[i] == InStream::CLOSE_BRACKET) { + if (closePos == -1) + closePos = int(i); + else + closePos = INT_MAX; + } + } + if (openPos != -1 && openPos != INT_MAX + && closePos != -1 && closePos != INT_MAX + && openPos < closePos) { + size_t index = message.find(extra, openPos); + if (index == std::string::npos || int(index) >= closePos) { + std::string result(message); + result.insert(closePos, ", " + extra); + return result; + } + return message; + } + + return message + " " + InStream::OPEN_BRACKET + extra + InStream::CLOSE_BRACKET; +} + +static std::string __testlib_toPrintableMessage(const std::string &message) { + int openPos = -1, closePos = -1; + for (size_t i = 0; i < message.length(); i++) { + if (message[i] == InStream::OPEN_BRACKET) { + if (openPos == -1) + openPos = int(i); + else + openPos = INT_MAX; + } + if (message[i] == InStream::CLOSE_BRACKET) { + if (closePos == -1) + closePos = int(i); + else + closePos = INT_MAX; + } + } + if (openPos != -1 && openPos != INT_MAX + && closePos != -1 && closePos != INT_MAX + && openPos < closePos) { + std::string result(message); + result[openPos] = '('; + result[closePos] = ')'; + return result; + } + + return message; +} + +NORETURN void InStream::quit(TResult result, const char *msg) { if (TestlibFinalizeGuard::alive) testlibFinalizeGuard.quitCount++; - if (mode != _output && result != _fail) - quits(_fail, std::string(msg) + " (" + name + ")"); + std::string message(msg); + message = trim(message); + + if (__testlib_hasTestCase) { + if (result != _ok) + message = __testlib_appendMessage(message, "test case " + vtos(__testlib_testCase)); + else { + if (__testlib_testCase == 1) + message = __testlib_appendMessage(message, vtos(__testlib_testCase) + " test case"); + else + message = __testlib_appendMessage(message, vtos(__testlib_testCase) + " test cases"); + } + } + + // You can change maxMessageLength. + // Example: 'inf.maxMessageLength = 1024 * 1024;'. + if (message.length() > maxMessageLength) { + std::string warn = "message length exceeds " + vtos(maxMessageLength) + + ", the message is truncated: "; + message = warn + message.substr(0, maxMessageLength - warn.length()); + } + +#ifndef ENABLE_UNEXPECTED_EOF + if (result == _unexpected_eof) + result = _pe; +#endif + + if (testlibMode == _scorer && result != _fail) + quits(_fail, "Scorer should return points only. Don't use a quit function."); + + if (mode != _output && result != _fail) { + if (mode == _input && testlibMode == _validator && lastLine != -1) + quits(_fail, __testlib_appendMessage(__testlib_appendMessage(message, name), "line " + vtos(lastLine))); + else + quits(_fail, __testlib_appendMessage(message, name)); + } - std::FILE * resultFile; + std::FILE *resultFile; std::string errorName; - - if (result == _ok) - { + + if (__testlib_shouldCheckDirt(result)) { if (testlibMode != _interactor && !ouf.seekEof()) quit(_dirt, "Extra information in the output file"); } @@ -1959,92 +3138,83 @@ NORETURN void InStream::quit(TResult result, const char* msg) int pctype = result - _partially; bool isPartial = false; - switch (result) - { - case _ok: - errorName = "ok "; - quitscrS(LightGreen, errorName); - break; - case _wa: - errorName = "wrong answer "; - quitscrS(LightRed, errorName); - break; - case _pe: - errorName = "wrong output format "; - quitscrS(LightRed, errorName); - break; - case _fail: - errorName = "FAIL "; - quitscrS(LightRed, errorName); - break; - case _dirt: - errorName = "wrong output format "; - quitscrS(LightCyan, errorName); - result = _pe; - break; - case _points: - errorName = "points "; - quitscrS(LightYellow, errorName); - break; - case _unexpected_eof: -#ifdef ENABLE_UNEXPECTED_EOF - errorName = "unexpected eof "; -#else - errorName = "wrong output format "; -#endif - quitscrS(LightCyan, errorName); - break; - default: - if (result >= _partially) - { - errorName = format("partially correct (%d) ", pctype); - isPartial = true; + switch (result) { + case _ok: + errorName = "ok "; + quitscrS(LightGreen, errorName); + break; + case _wa: + errorName = "wrong answer "; + quitscrS(LightRed, errorName); + break; + case _pe: + errorName = "wrong output format "; + quitscrS(LightRed, errorName); + break; + case _fail: + errorName = "FAIL "; + quitscrS(LightRed, errorName); + break; + case _dirt: + errorName = "wrong output format "; + quitscrS(LightCyan, errorName); + result = _pe; + break; + case _points: + errorName = "points "; quitscrS(LightYellow, errorName); - } - else - quit(_fail, "What is the code ??? "); + break; + case _unexpected_eof: + errorName = "unexpected eof "; + quitscrS(LightCyan, errorName); + break; + default: + if (result >= _partially) { + errorName = testlib_format_("partially correct (%d) ", pctype); + isPartial = true; + quitscrS(LightYellow, errorName); + } else + quit(_fail, "What is the code ??? "); } - if (resultName != "") - { - resultFile = __testlib_fopen(resultName.c_str(), "w"); - if (resultFile == NULL) + if (resultName != "") { + resultFile = testlib_fopen_(resultName.c_str(), "w"); + if (resultFile == NULL) { + resultName = ""; quit(_fail, "Can not write to the result file"); - if (appesMode) - { - std::fprintf(resultFile, ""); + } + if (appesMode) { + std::fprintf(resultFile, "", appesModeEncoding.c_str()); if (isPartial) - std::fprintf(resultFile, "", outcomes[(int)_partially].c_str(), pctype); - else - { + std::fprintf(resultFile, "", + outcomes[(int) _partially].c_str(), pctype); + else { if (result != _points) - std::fprintf(resultFile, "", outcomes[(int)result].c_str()); - else - { + std::fprintf(resultFile, "", outcomes[(int) result].c_str()); + else { if (__testlib_points == std::numeric_limits::infinity()) quit(_fail, "Expected points, but infinity found"); - std::string stringPoints = removeDoubleTrailingZeroes(format("%.10f", __testlib_points)); - std::fprintf(resultFile, "", outcomes[(int)result].c_str(), stringPoints.c_str()); + std::string stringPoints = removeDoubleTrailingZeroes(testlib_format_("%.10f", __testlib_points)); + std::fprintf(resultFile, "", + outcomes[(int) result].c_str(), stringPoints.c_str()); } } - xmlSafeWrite(resultFile, msg); + xmlSafeWrite(resultFile, __testlib_toPrintableMessage(message).c_str()); std::fprintf(resultFile, "\n"); - } - else - std::fprintf(resultFile, "%s", msg); - if (NULL == resultFile || fclose(resultFile) != 0) + } else + std::fprintf(resultFile, "%s", __testlib_toPrintableMessage(message).c_str()); + if (NULL == resultFile || fclose(resultFile) != 0) { + resultName = ""; quit(_fail, "Can not write to the result file"); + } } - quitscr(LightGray, msg); + quitscr(LightGray, __testlib_toPrintableMessage(message).c_str()); std::fprintf(stderr, "\n"); - if (inf.file) - fclose(inf.file); - if (ouf.file) - fclose(ouf.file); - if (ans.file) - fclose(ans.file); + inf.close(); + ouf.close(); + ans.close(); if (tout.is_open()) tout.close(); @@ -2057,46 +3227,47 @@ NORETURN void InStream::quit(TResult result, const char* msg) } #ifdef __GNUC__ - __attribute__ ((format (printf, 3, 4))) +__attribute__ ((format (printf, 3, 4))) #endif -NORETURN void InStream::quitf(TResult result, const char* msg, ...) -{ +NORETURN void InStream::quitf(TResult result, const char *msg, ...) { FMT_TO_RESULT(msg, msg, message); InStream::quit(result, message.c_str()); } -NORETURN void InStream::quits(TResult result, std::string msg) -{ +#ifdef __GNUC__ +__attribute__ ((format (printf, 4, 5))) +#endif +void InStream::quitif(bool condition, TResult result, const char *msg, ...) { + if (condition) { + FMT_TO_RESULT(msg, msg, message); + InStream::quit(result, message.c_str()); + } +} + +NORETURN void InStream::quits(TResult result, std::string msg) { InStream::quit(result, msg.c_str()); } -void InStream::xmlSafeWrite(std::FILE * file, const char* msg) -{ +void InStream::xmlSafeWrite(std::FILE *file, const char *msg) { size_t lmsg = strlen(msg); - for (size_t i = 0; i < lmsg; i++) - { - if (msg[i] == '&') - { + for (size_t i = 0; i < lmsg; i++) { + if (msg[i] == '&') { std::fprintf(file, "%s", "&"); continue; } - if (msg[i] == '<') - { + if (msg[i] == '<') { std::fprintf(file, "%s", "<"); continue; } - if (msg[i] == '>') - { + if (msg[i] == '>') { std::fprintf(file, "%s", ">"); continue; } - if (msg[i] == '"') - { + if (msg[i] == '"') { std::fprintf(file, "%s", """); continue; } - if (0 <= msg[i] && msg[i] <= 31) - { + if (0 <= msg[i] && msg[i] <= 31) { std::fprintf(file, "%c", '.'); continue; } @@ -2104,150 +3275,165 @@ void InStream::xmlSafeWrite(std::FILE * file, const char* msg) } } -void InStream::quitscrS(WORD color, std::string msg) -{ +void InStream::quitscrS(WORD color, std::string msg) { quitscr(color, msg.c_str()); } -void InStream::quitscr(WORD color, const char* msg) -{ - if (resultName == "") - { +void InStream::quitscr(WORD color, const char *msg) { + if (resultName == "") { textColor(color); std::fprintf(stderr, "%s", msg); textColor(LightGray); } } -void InStream::reset() -{ +void InStream::reset(std::FILE *file) { if (opened && stdfile) quit(_fail, "Can't reset standard handle"); if (opened) close(); - if (!stdfile) - if (NULL == (file = __testlib_fopen(name.c_str(), "rb"))) - { + if (!stdfile && NULL == file) + if (NULL == (file = testlib_fopen_(name.c_str(), "rb"))) { if (mode == _output) - quits(_pe, std::string("File not found: \"") + name + "\""); - } + quits(_pe, std::string("Output file not found: \"") + name + "\""); - opened = true; + if (mode == _answer) + quits(_fail, std::string("Answer file not found: \"") + name + "\""); + } - __testlib_set_binary(file); + if (NULL != file) { + opened = true; + __testlib_set_binary(file); - if (stdfile) - reader = new FileInputStreamReader(file, name); - else - reader = new BufferedFileInputStreamReader(file, name); + if (stdfile) + reader = new FileInputStreamReader(file, name); + else + reader = new BufferedFileInputStreamReader(file, name); + } else { + opened = false; + reader = NULL; + } } -void InStream::init(std::string fileName, TMode mode) -{ +void InStream::init(std::string fileName, TMode mode) { + FILE *file = NULL; opened = false; - if (fileName == "/dev/stdin") - { - name = "stdin"; - this->file = stdin; - stdfile = true; - } - else - { - name = fileName; - stdfile = false; - } + if (fileName == "/dev/stdin") { + name = "stdin"; + file = stdin; + stdfile = true; + } else { + name = fileName; + stdfile = false; + } this->mode = mode; - reset(); + + std::ifstream stream; + stream.open(fileName.c_str(), std::ios::in); + if (stream.is_open()) { + std::streampos start = stream.tellg(); + stream.seekg(0, std::ios::end); + std::streampos end = stream.tellg(); + size_t fileSize = size_t(end - start); + stream.close(); + + // You can change maxFileSize. + // Example: 'inf.maxFileSize = 256 * 1024 * 1024;'. + if (fileSize > maxFileSize) + quitf(_pe, "File size exceeds %d bytes, size is %d", int(maxFileSize), int(fileSize)); + } + + reset(file); } -void InStream::init(std::FILE* f, TMode mode) -{ +void InStream::init(std::FILE *f, TMode mode) { opened = false; - name = "untitled"; - + this->mode = mode; + if (f == stdin) name = "stdin", stdfile = true; - if (f == stdout) name = "stdout", stdfile = true; - if (f == stderr) name = "stderr", stdfile = true; - this->file = f; - this->mode = mode; - - reset(); + reset(f); } -char InStream::curChar() -{ - int c = reader->curChar(); - return char(c != EOF ? c : EOFC); +void InStream::skipBom() { + const std::string utf8Bom = "\xEF\xBB\xBF"; + size_t index = 0; + while (index < utf8Bom.size() && curChar() == utf8Bom[index]) { + index++; + skipChar(); + } + if (index < utf8Bom.size()) { + while (index != 0) { + unreadChar(utf8Bom[index - 1]); + index--; + } + } } -char InStream::nextChar() -{ - int c = reader->nextChar(); - return char(c != EOF ? c : EOFC); +char InStream::curChar() { + return char(reader->curChar()); } -char InStream::readChar() -{ +char InStream::nextChar() { + return char(reader->nextChar()); +} + +char InStream::readChar() { return nextChar(); } -char InStream::readChar(char c) -{ +char InStream::readChar(char c) { + lastLine = reader->getLine(); char found = readChar(); - if (c != found) - { + if (c != found) { if (!isEoln(found)) - quit(_pe, ("Unexpected character '" + std::string(1, found) + "', but '" + std::string(1, c) + "' expected").c_str()); + quit(_pe, ("Unexpected character '" + std::string(1, found) + "', but '" + std::string(1, c) + + "' expected").c_str()); else - quit(_pe, ("Unexpected character " + ("#" + vtos(int(found))) + ", but '" + std::string(1, c) + "' expected").c_str()); + quit(_pe, ("Unexpected character " + ("#" + vtos(int(found))) + ", but '" + std::string(1, c) + + "' expected").c_str()); } return found; } -char InStream::readSpace() -{ +char InStream::readSpace() { return readChar(' '); } -void InStream::unreadChar(char c) -{ +void InStream::unreadChar(char c) { reader->unreadChar(c); } -void InStream::skipChar() -{ +void InStream::skipChar() { reader->skipChar(); } -void InStream::skipBlanks() -{ +void InStream::skipBlanks() { while (isBlanks(reader->curChar())) reader->skipChar(); } -std::string InStream::readWord() -{ +std::string InStream::readWord() { readWordTo(_tmpReadToken); return _tmpReadToken; } -void InStream::readWordTo(std::string& result) -{ +void InStream::readWordTo(std::string &result) { if (!strict) skipBlanks(); + lastLine = reader->getLine(); int cur = reader->nextChar(); - if (cur == EOF) + if (cur == EOFC) quit(_unexpected_eof, "Unexpected end of file - token expected"); if (isBlanks(cur)) @@ -2255,9 +3441,15 @@ void InStream::readWordTo(std::string& result) result.clear(); - while (!(isBlanks(cur) || cur == EOF)) - { + while (!(isBlanks(cur) || cur == EOFC)) { result += char(cur); + + // You can change maxTokenLength. + // Example: 'inf.maxTokenLength = 128 * 1024 * 1024;'. + if (result.length() > maxTokenLength) + quitf(_pe, "Length of token exceeds %d, token is '%s...'", int(maxTokenLength), + __testlib_part(result).c_str()); + cur = reader->nextChar(); } @@ -2267,84 +3459,158 @@ void InStream::readWordTo(std::string& result) quit(_unexpected_eof, "Unexpected end of file or white-space - token expected"); } -std::string InStream::readToken() -{ +std::string InStream::readToken() { return readWord(); } -void InStream::readTokenTo(std::string& result) -{ +void InStream::readTokenTo(std::string &result) { readWordTo(result); } -static std::string __testlib_part(const std::string& s) -{ - if (s.length() <= 64) - return s; +#ifdef __GNUC__ +__attribute__((const)) +#endif +static std::string __testlib_part(const std::string &s) { + std::string t; + for (size_t i = 0; i < s.length(); i++) + if (s[i] != '\0') + t += s[i]; + else + t += '~'; + if (t.length() <= 64) + return t; else - return s.substr(0, 30) + "..." + s.substr(s.length() - 31, 31); -} - -std::string InStream::readWord(const pattern& p, const std::string& variableName) -{ + return t.substr(0, 30) + "..." + t.substr(s.length() - 31, 31); +} + +#define __testlib_readMany(readMany, readOne, typeName, space) \ + if (size < 0) \ + quit(_fail, #readMany ": size should be non-negative."); \ + if (size > 100000000) \ + quit(_fail, #readMany ": size should be at most 100000000."); \ + \ + std::vector result(size); \ + readManyIteration = indexBase; \ + \ + for (int i = 0; i < size; i++) \ + { \ + result[i] = readOne; \ + readManyIteration++; \ + if (strict && space && i + 1 < size) \ + readSpace(); \ + } \ + \ + readManyIteration = NO_INDEX; \ + return result; \ + + +std::string InStream::readWord(const pattern &p, const std::string &variableName) { readWordTo(_tmpReadToken); - if (!p.matches(_tmpReadToken)) - { - if (variableName.empty()) - quit(_wa, ("Token \"" + __testlib_part(_tmpReadToken) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); - else - quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(_tmpReadToken) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + if (!p.matches(_tmpReadToken)) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty()) + quit(_wa, + ("Token \"" + __testlib_part(_tmpReadToken) + "\" doesn't correspond to pattern \"" + p.src() + + "\"").c_str()); + else + quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(_tmpReadToken) + + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + } else { + if (variableName.empty()) + quit(_wa, ("Token element [index=" + vtos(readManyIteration) + "] equals to \"" + + __testlib_part(_tmpReadToken) + "\" doesn't correspond to pattern \"" + p.src() + + "\"").c_str()); + else + quit(_wa, ("Token element " + variableName + "[" + vtos(readManyIteration) + "] equals to \"" + + __testlib_part(_tmpReadToken) + "\", doesn't correspond to pattern \"" + p.src() + + "\"").c_str()); + } } + if (strict && !variableName.empty()) + validator.addVariable(variableName); return _tmpReadToken; } -std::string InStream::readWord(const std::string& ptrn, const std::string& variableName) -{ +std::vector +InStream::readWords(int size, const pattern &p, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readWords, readWord(p, variablesName), std::string, true); +} + +std::vector InStream::readWords(int size, int indexBase) { + __testlib_readMany(readWords, readWord(), std::string, true); +} + +std::string InStream::readWord(const std::string &ptrn, const std::string &variableName) { return readWord(pattern(ptrn), variableName); } -std::string InStream::readToken(const pattern& p, const std::string& variableName) -{ +std::vector +InStream::readWords(int size, const std::string &ptrn, const std::string &variablesName, int indexBase) { + pattern p(ptrn); + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readWords, readWord(p, variablesName), std::string, true); +} + +std::string InStream::readToken(const pattern &p, const std::string &variableName) { return readWord(p, variableName); } -std::string InStream::readToken(const std::string& ptrn, const std::string& variableName) -{ +std::vector +InStream::readTokens(int size, const pattern &p, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readTokens, readToken(p, variablesName), std::string, true); +} + +std::vector InStream::readTokens(int size, int indexBase) { + __testlib_readMany(readTokens, readToken(), std::string, true); +} + +std::string InStream::readToken(const std::string &ptrn, const std::string &variableName) { return readWord(ptrn, variableName); } -void InStream::readWordTo(std::string& result, const pattern& p, const std::string& variableName) -{ +std::vector +InStream::readTokens(int size, const std::string &ptrn, const std::string &variablesName, int indexBase) { + pattern p(ptrn); + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readTokens, readWord(p, variablesName), std::string, true); +} + +void InStream::readWordTo(std::string &result, const pattern &p, const std::string &variableName) { readWordTo(result); - if (!p.matches(result)) - { + if (!p.matches(result)) { if (variableName.empty()) - quit(_wa, ("Token \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + quit(_wa, ("Token \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + + "\"").c_str()); else - quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + quit(_wa, ("Token parameter [name=" + variableName + "] equals to \"" + __testlib_part(result) + + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); } + if (strict && !variableName.empty()) + validator.addVariable(variableName); } -void InStream::readWordTo(std::string& result, const std::string& ptrn, const std::string& variableName) -{ +void InStream::readWordTo(std::string &result, const std::string &ptrn, const std::string &variableName) { return readWordTo(result, pattern(ptrn), variableName); } -void InStream::readTokenTo(std::string& result, const pattern& p, const std::string& variableName) -{ +void InStream::readTokenTo(std::string &result, const pattern &p, const std::string &variableName) { return readWordTo(result, p, variableName); } -void InStream::readTokenTo(std::string& result, const std::string& ptrn, const std::string& variableName) -{ +void InStream::readTokenTo(std::string &result, const std::string &ptrn, const std::string &variableName) { return readWordTo(result, ptrn, variableName); } #ifdef __GNUC__ __attribute__((pure)) #endif -static inline bool equals(long long integer, const char* s) -{ +static inline bool equals(long long integer, const char *s) { if (integer == LLONG_MIN) return strcmp(s, "-9223372036854775808") == 0; @@ -2365,8 +3631,7 @@ static inline bool equals(long long integer, const char* s) if (length == 0) return false; - while (integer > 0) - { + while (integer > 0) { int digit = int(integer % 10); if (s[length - 1] != '0' + digit) @@ -2379,9 +3644,36 @@ static inline bool equals(long long integer, const char* s) return length == 0; } -static inline double stringToDouble(InStream& in, const char* buffer) -{ - double retval; +#ifdef __GNUC__ +__attribute__((pure)) +#endif +static inline bool equals(unsigned long long integer, const char *s) { + if (integer == ULLONG_MAX) + return strcmp(s, "18446744073709551615") == 0; + + if (integer == 0ULL) + return strcmp(s, "0") == 0; + + size_t length = strlen(s); + + if (length == 0) + return false; + + while (integer > 0) { + int digit = int(integer % 10); + + if (s[length - 1] != '0' + digit) + return false; + + length--; + integer /= 10; + } + + return length == 0; +} + +static inline double stringToDouble(InStream &in, const char *buffer) { + double result; size_t length = strlen(buffer); @@ -2391,12 +3683,10 @@ static inline double stringToDouble(InStream& in, const char* buffer) int digitCount = 0; int eCount = 0; - for (size_t i = 0; i < length; i++) - { + for (size_t i = 0; i < length; i++) { if (('0' <= buffer[i] && buffer[i] <= '9') || buffer[i] == '.' - || buffer[i] == 'e' || buffer[i] == 'E' - || buffer[i] == '-' || buffer[i] == '+') - { + || buffer[i] == 'e' || buffer[i] == 'E' + || buffer[i] == '-' || buffer[i] == '+') { if ('0' <= buffer[i] && buffer[i] <= '9') digitCount++; if (buffer[i] == 'e' || buffer[i] == 'E') @@ -2407,8 +3697,7 @@ static inline double stringToDouble(InStream& in, const char* buffer) plusCount++; if (buffer[i] == '.') decimalPointCount++; - } - else + } else in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); } @@ -2416,30 +3705,42 @@ static inline double stringToDouble(InStream& in, const char* buffer) if (digitCount == 0 || minusCount > 2 || plusCount > 2 || decimalPointCount > 1 || eCount > 1) in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); - char* suffix = new char[length + 1]; - int scanned = std::sscanf(buffer, "%lf%s", &retval, suffix); + char *suffix = new char[length + 1]; + std::memset(suffix, 0, length + 1); + int scanned; +#ifdef _MSC_VER + scanned = sscanf_s(buffer, "%lf%s", &result, suffix, (unsigned int)(length + 1)); +#else + scanned = std::sscanf(buffer, "%lf%s", &result, suffix); +#endif bool empty = strlen(suffix) == 0; delete[] suffix; - if (scanned == 1 || (scanned == 2 && empty)) - { - if (__testlib_isNaN(retval) || __testlib_isInfinite(retval)) + if (scanned == 1 || (scanned == 2 && empty)) { + if (__testlib_isNaN(result)) in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); - return retval; - } - else + return result; + } else in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); } -static inline double stringToStrictDouble(InStream& in, const char* buffer, int minAfterPointDigitCount, int maxAfterPointDigitCount) -{ +static inline double stringToDouble(InStream &in, const std::string& buffer) { + for (size_t i = 0; i < buffer.length(); i++) + if (buffer[i] == '\0') + in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found (it contains \\0)").c_str()); + return stringToDouble(in, buffer.c_str()); +} + +static inline double stringToStrictDouble(InStream &in, const char *buffer, + int minAfterPointDigitCount, int maxAfterPointDigitCount) { if (minAfterPointDigitCount < 0) in.quit(_fail, "stringToStrictDouble: minAfterPointDigitCount should be non-negative."); - + if (minAfterPointDigitCount > maxAfterPointDigitCount) - in.quit(_fail, "stringToStrictDouble: minAfterPointDigitCount should be less or equal to maxAfterPointDigitCount."); + in.quit(_fail, + "stringToStrictDouble: minAfterPointDigitCount should be less or equal to maxAfterPointDigitCount."); - double retval; + double result; size_t length = strlen(buffer); @@ -2449,11 +3750,9 @@ static inline double stringToStrictDouble(InStream& in, const char* buffer, int if (buffer[0] != '-' && (buffer[0] < '0' || buffer[0] > '9')) in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); - int pointPos = -1; - for (size_t i = 1; i + 1 < length; i++) - { - if (buffer[i] == '.') - { + int pointPos = -1; + for (size_t i = 1; i + 1 < length; i++) { + if (buffer[i] == '.') { if (pointPos > -1) in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); pointPos = int(i); @@ -2468,63 +3767,65 @@ static inline double stringToStrictDouble(InStream& in, const char* buffer, int int afterDigitsCount = (pointPos == -1 ? 0 : int(length) - pointPos - 1); if (afterDigitsCount < minAfterPointDigitCount || afterDigitsCount > maxAfterPointDigitCount) in.quit(_pe, ("Expected strict double with number of digits after point in range [" - + vtos(minAfterPointDigitCount) - + "," - + vtos(maxAfterPointDigitCount) - + "], but \"" + __testlib_part(buffer) + "\" found").c_str() + + vtos(minAfterPointDigitCount) + + "," + + vtos(maxAfterPointDigitCount) + + "], but \"" + __testlib_part(buffer) + "\" found").c_str() ); int firstDigitPos = -1; for (size_t i = 0; i < length; i++) - if (buffer[i] >= '0' && buffer[i] <= '9') - { + if (buffer[i] >= '0' && buffer[i] <= '9') { firstDigitPos = int(i); break; } - if (firstDigitPos > 1 || firstDigitPos == -1) + if (firstDigitPos > 1 || firstDigitPos == -1) in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); if (buffer[firstDigitPos] == '0' && firstDigitPos + 1 < int(length) - && buffer[firstDigitPos + 1] >= '0' && buffer[firstDigitPos + 1] <= '9') + && buffer[firstDigitPos + 1] >= '0' && buffer[firstDigitPos + 1] <= '9') in.quit(_pe, ("Expected strict double, but \"" + __testlib_part(buffer) + "\" found").c_str()); - char* suffix = new char[length + 1]; - int scanned = std::sscanf(buffer, "%lf%s", &retval, suffix); + char *suffix = new char[length + 1]; + std::memset(suffix, 0, length + 1); + int scanned; +#ifdef _MSC_VER + scanned = sscanf_s(buffer, "%lf%s", &result, suffix, (unsigned int)(length + 1)); +#else + scanned = std::sscanf(buffer, "%lf%s", &result, suffix); +#endif bool empty = strlen(suffix) == 0; delete[] suffix; - if (scanned == 1 || (scanned == 2 && empty)) - { - if (__testlib_isNaN(retval) || __testlib_isInfinite(retval)) + if (scanned == 1 || (scanned == 2 && empty)) { + if (__testlib_isNaN(result) || __testlib_isInfinite(result)) in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); - return retval; - } - else + if (buffer[0] == '-' && result >= 0) + in.quit(_pe, ("Redundant minus in \"" + __testlib_part(buffer) + "\" found").c_str()); + return result; + } else in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found").c_str()); } -static inline long long stringToLongLong(InStream& in, const char* buffer) -{ - if (strcmp(buffer, "-9223372036854775808") == 0) - return LLONG_MIN; +static inline double stringToStrictDouble(InStream &in, const std::string& buffer, + int minAfterPointDigitCount, int maxAfterPointDigitCount) { + for (size_t i = 0; i < buffer.length(); i++) + if (buffer[i] == '\0') + in.quit(_pe, ("Expected double, but \"" + __testlib_part(buffer) + "\" found (it contains \\0)").c_str()); + return stringToStrictDouble(in, buffer.c_str(), minAfterPointDigitCount, maxAfterPointDigitCount); +} - bool minus = false; +static inline long long stringToLongLong(InStream &in, const char *buffer) { size_t length = strlen(buffer); - - if (length > 1 && buffer[0] == '-') - minus = true; - - if (length > 20) + if (length == 0 || length > 20) in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); - long long retval = 0LL; - + bool has_minus = (length > 1 && buffer[0] == '-'); int zeroes = 0; - int processingZeroes = true; - - for (int i = (minus ? 1 : 0); i < int(length); i++) - { + bool processingZeroes = true; + + for (int i = (has_minus ? 1 : 0); i < int(length); i++) { if (buffer[i] == '0' && processingZeroes) zeroes++; else @@ -2532,219 +3833,438 @@ static inline long long stringToLongLong(InStream& in, const char* buffer) if (buffer[i] < '0' || buffer[i] > '9') in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); - retval = retval * 10 + (buffer[i] - '0'); } - if (retval < 0) + long long int result; + try { + result = std::stoll(buffer); + } catch (const std::exception&) { in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); - - if ((zeroes > 0 && (retval != 0 || minus)) || zeroes > 1) + } catch (...) { in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + } - retval = (minus ? -retval : +retval); - - if (length < 19) - return retval; + if ((zeroes > 0 && (result != 0 || has_minus)) || zeroes > 1) + in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); - if (equals(retval, buffer)) - return retval; - else - in.quit(_pe, ("Expected int64, but \"" + __testlib_part(buffer) + "\" found").c_str()); + return result; } -int InStream::readInteger() -{ - if (!strict && seekEof()) - quit(_unexpected_eof, "Unexpected end of file - int32 expected"); - - readWordTo(_tmpReadToken); - - long long value = stringToLongLong(*this, _tmpReadToken.c_str()); - if (value < INT_MIN || value > INT_MAX) - quit(_pe, ("Expected int32, but \"" + __testlib_part(_tmpReadToken) + "\" found").c_str()); - - return int(value); +static inline long long stringToLongLong(InStream &in, const std::string& buffer) { + for (size_t i = 0; i < buffer.length(); i++) + if (buffer[i] == '\0') + in.quit(_pe, ("Expected integer, but \"" + __testlib_part(buffer) + "\" found (it contains \\0)").c_str()); + return stringToLongLong(in, buffer.c_str()); } -long long InStream::readLong() -{ +static inline unsigned long long stringToUnsignedLongLong(InStream &in, const char *buffer) { + size_t length = strlen(buffer); + + if (length == 0 || length > 20) + in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + if (length > 1 && buffer[0] == '0') + in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + + for (int i = 0; i < int(length); i++) { + if (buffer[i] < '0' || buffer[i] > '9') + in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + } + + unsigned long long result; + try { + result = std::stoull(buffer); + } catch (const std::exception&) { + in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + } catch (...) { + in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found").c_str()); + } + + return result; +} + +static inline long long stringToUnsignedLongLong(InStream &in, const std::string& buffer) { + for (size_t i = 0; i < buffer.length(); i++) + if (buffer[i] == '\0') + in.quit(_pe, ("Expected unsigned integer, but \"" + __testlib_part(buffer) + "\" found (it contains \\0)").c_str()); + return stringToUnsignedLongLong(in, buffer.c_str()); +} + +int InStream::readInteger() { + if (!strict && seekEof()) + quit(_unexpected_eof, "Unexpected end of file - int32 expected"); + + readWordTo(_tmpReadToken); + + long long value = stringToLongLong(*this, _tmpReadToken); + if (value < INT_MIN || value > INT_MAX) + quit(_pe, ("Expected int32, but \"" + __testlib_part(_tmpReadToken) + "\" found").c_str()); + + return int(value); +} + +long long InStream::readLong() { if (!strict && seekEof()) quit(_unexpected_eof, "Unexpected end of file - int64 expected"); readWordTo(_tmpReadToken); - return stringToLongLong(*this, _tmpReadToken.c_str()); + + return stringToLongLong(*this, _tmpReadToken); } -long long InStream::readLong(long long minv, long long maxv, const std::string& variableName) -{ +unsigned long long InStream::readUnsignedLong() { + if (!strict && seekEof()) + quit(_unexpected_eof, "Unexpected end of file - int64 expected"); + + readWordTo(_tmpReadToken); + + return stringToUnsignedLongLong(*this, _tmpReadToken); +} + +long long InStream::readLong(long long minv, long long maxv, const std::string &variableName) { long long result = readLong(); - if (result < minv || result > maxv) - { - if (variableName.empty()) - quit(_wa, ("Integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); - else - quit(_wa, ("Integer parameter [name=" + variableName + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty()) + quit(_wa, ("Integer " + vtos(result) + " violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + + "]").c_str()); + else + quit(_wa, ("Integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); + } else { + if (variableName.empty()) + quit(_wa, ("Integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); + else + quit(_wa, + ("Integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + + vtos(result) + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); + } + } + + if (strict && !variableName.empty()) { + validator.addBoundsHit(variableName, ValidatorBoundsHit(minv == result, maxv == result)); + validator.adjustConstantBounds(variableName, minv, maxv); + validator.addVariable(variableName); } return result; } -int InStream::readInt() -{ +std::vector +InStream::readLongs(int size, long long minv, long long maxv, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readLongs, readLong(minv, maxv, variablesName), long long, true) +} + +std::vector InStream::readLongs(int size, int indexBase) { + __testlib_readMany(readLongs, readLong(), long long, true) +} + +unsigned long long +InStream::readUnsignedLong(unsigned long long minv, unsigned long long maxv, const std::string &variableName) { + unsigned long long result = readUnsignedLong(); + + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty()) + quit(_wa, + ("Unsigned integer " + vtos(result) + " violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + + "]").c_str()); + else + quit(_wa, + ("Unsigned integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); + } else { + if (variableName.empty()) + quit(_wa, + ("Unsigned integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); + else + quit(_wa, ("Unsigned integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + + "] equals to " + vtos(result) + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + + "]").c_str()); + } + } + + if (strict && !variableName.empty()) { + validator.addBoundsHit(variableName, ValidatorBoundsHit(minv == result, maxv == result)); + validator.adjustConstantBounds(variableName, minv, maxv); + validator.addVariable(variableName); + } + + return result; +} + +std::vector InStream::readUnsignedLongs(int size, unsigned long long minv, unsigned long long maxv, + const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readUnsignedLongs, readUnsignedLong(minv, maxv, variablesName), unsigned long long, true) +} + +std::vector InStream::readUnsignedLongs(int size, int indexBase) { + __testlib_readMany(readUnsignedLongs, readUnsignedLong(), unsigned long long, true) +} + +unsigned long long +InStream::readLong(unsigned long long minv, unsigned long long maxv, const std::string &variableName) { + return readUnsignedLong(minv, maxv, variableName); +} + +int InStream::readInt() { return readInteger(); } -int InStream::readInt(int minv, int maxv, const std::string &variableName) -{ +int InStream::readInt(int minv, int maxv, const std::string &variableName) { int result = readInt(); - if (result < minv || result > maxv) - { - if (variableName.empty()) - quit(_wa, ("Integer " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); - else - quit(_wa, ("Integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); - } + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty()) + quit(_wa, ("Integer " + vtos(result) + " violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + + "]").c_str()); + else + quit(_wa, ("Integer parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); + } else { + if (variableName.empty()) + quit(_wa, ("Integer element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); + else + quit(_wa, + ("Integer element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + + vtos(result) + ", violates the range [" + toHumanReadableString(minv) + ", " + toHumanReadableString(maxv) + "]").c_str()); + } + } + + if (strict && !variableName.empty()) { + validator.addBoundsHit(variableName, ValidatorBoundsHit(minv == result, maxv == result)); + validator.adjustConstantBounds(variableName, minv, maxv); + validator.addVariable(variableName); + } return result; } -int InStream::readInteger(int minv, int maxv, const std::string& variableName) -{ - return readInt(minv, maxv, variableName.c_str()); +int InStream::readInteger(int minv, int maxv, const std::string &variableName) { + return readInt(minv, maxv, variableName); } -double InStream::readReal() -{ +std::vector InStream::readInts(int size, int minv, int maxv, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readInts, readInt(minv, maxv, variablesName), int, true) +} + +std::vector InStream::readInts(int size, int indexBase) { + __testlib_readMany(readInts, readInt(), int, true) +} + +std::vector InStream::readIntegers(int size, int minv, int maxv, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readIntegers, readInt(minv, maxv, variablesName), int, true) +} + +std::vector InStream::readIntegers(int size, int indexBase) { + __testlib_readMany(readIntegers, readInt(), int, true) +} + +double InStream::readReal() { if (!strict && seekEof()) quit(_unexpected_eof, "Unexpected end of file - double expected"); - return stringToDouble(*this, readWord().c_str()); + return stringToDouble(*this, readWord()); } -double InStream::readDouble() -{ +double InStream::readDouble() { return readReal(); } -double InStream::readReal(double minv, double maxv, const std::string& variableName) -{ +double InStream::readReal(double minv, double maxv, const std::string &variableName) { double result = readReal(); - if (result < minv || result > maxv) - { - if (variableName.empty()) - quit(_wa, ("Double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); - else - quit(_wa, ("Double parameter [name=" + variableName + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty()) + quit(_wa, ("Double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + + "]").c_str()); + else + quit(_wa, ("Double parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } else { + if (variableName.empty()) + quit(_wa, ("Double element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else + quit(_wa, + ("Double element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to " + + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } } + if (strict && !variableName.empty()) { + validator.addBoundsHit(variableName, ValidatorBoundsHit( + doubleDelta(minv, result) < ValidatorBoundsHit::EPS, + doubleDelta(maxv, result) < ValidatorBoundsHit::EPS + )); + validator.adjustConstantBounds(variableName, minv, maxv); + validator.addVariable(variableName); + } + return result; } -double InStream::readDouble(double minv, double maxv, const std::string& variableName) -{ +std::vector +InStream::readReals(int size, double minv, double maxv, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readReals, readReal(minv, maxv, variablesName), double, true) +} + +std::vector InStream::readReals(int size, int indexBase) { + __testlib_readMany(readReals, readReal(), double, true) +} + +double InStream::readDouble(double minv, double maxv, const std::string &variableName) { return readReal(minv, maxv, variableName); -} +} + +std::vector +InStream::readDoubles(int size, double minv, double maxv, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readDoubles, readDouble(minv, maxv, variablesName), double, true) +} + +std::vector InStream::readDoubles(int size, int indexBase) { + __testlib_readMany(readDoubles, readDouble(), double, true) +} double InStream::readStrictReal(double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variableName) -{ + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variableName) { if (!strict && seekEof()) quit(_unexpected_eof, "Unexpected end of file - strict double expected"); - double result = stringToStrictDouble(*this, readWord().c_str(), - minAfterPointDigitCount, maxAfterPointDigitCount); + double result = stringToStrictDouble(*this, readWord(), minAfterPointDigitCount, maxAfterPointDigitCount); - if (result < minv || result > maxv) - { - if (variableName.empty()) - quit(_wa, ("Strict double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); - else - quit(_wa, ("Strict double parameter [name=" + variableName + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + if (result < minv || result > maxv) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty()) + quit(_wa, ("Strict double " + vtos(result) + " violates the range [" + vtos(minv) + ", " + vtos(maxv) + + "]").c_str()); + else + quit(_wa, + ("Strict double parameter [name=" + std::string(variableName) + "] equals to " + vtos(result) + + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + } else { + if (variableName.empty()) + quit(_wa, ("Strict double element [index=" + vtos(readManyIteration) + "] equals to " + vtos(result) + + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + "]").c_str()); + else + quit(_wa, ("Strict double element " + std::string(variableName) + "[" + vtos(readManyIteration) + + "] equals to " + vtos(result) + ", violates the range [" + vtos(minv) + ", " + vtos(maxv) + + "]").c_str()); + } + } + + if (strict && !variableName.empty()) { + validator.addBoundsHit(variableName, ValidatorBoundsHit( + doubleDelta(minv, result) < ValidatorBoundsHit::EPS, + doubleDelta(maxv, result) < ValidatorBoundsHit::EPS + )); + validator.adjustConstantBounds(variableName, minv, maxv); + validator.addVariable(variableName); } return result; } +std::vector InStream::readStrictReals(int size, double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readStrictReals, + readStrictReal(minv, maxv, minAfterPointDigitCount, maxAfterPointDigitCount, variablesName), + double, true) +} + double InStream::readStrictDouble(double minv, double maxv, - int minAfterPointDigitCount, int maxAfterPointDigitCount, - const std::string& variableName) -{ + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variableName) { return readStrictReal(minv, maxv, - minAfterPointDigitCount, maxAfterPointDigitCount, - variableName); + minAfterPointDigitCount, maxAfterPointDigitCount, + variableName); } -bool InStream::eof() -{ - if (!strict && NULL == file) +std::vector InStream::readStrictDoubles(int size, double minv, double maxv, + int minAfterPointDigitCount, int maxAfterPointDigitCount, + const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readStrictDoubles, + readStrictDouble(minv, maxv, minAfterPointDigitCount, maxAfterPointDigitCount, variablesName), + double, true) +} + +bool InStream::eof() { + if (!strict && NULL == reader) return true; return reader->eof(); } -bool InStream::seekEof() -{ - if (NULL == file) +bool InStream::seekEof() { + if (!strict && NULL == reader) return true; skipBlanks(); return eof(); } -bool InStream::eoln() -{ - if (!strict && NULL == file) +bool InStream::eoln() { + if (!strict && NULL == reader) return true; int c = reader->nextChar(); - if (!strict) - { - if (c == EOF) + if (!strict) { + if (c == EOFC) return true; - if (c == CR) - { + if (c == CR) { c = reader->nextChar(); - if (c != LF) - { + if (c != LF) { reader->unreadChar(c); reader->unreadChar(CR); return false; - } - else + } else return true; } - + if (c == LF) return true; reader->unreadChar(c); return false; - } - else - { + } else { bool returnCr = false; -#ifdef CR_MUST_IN_EOL - if (c != CR) - { +#if (defined(ON_WINDOWS) && !defined(FOR_LINUX)) || defined(FOR_WINDOWS) + if (c != CR) { reader->unreadChar(c); return false; - } - else - { + } else { if (!returnCr) returnCr = true; c = reader->nextChar(); } -#endif - if (c != LF) - { +#endif + if (c != LF) { reader->unreadChar(c); if (returnCr) reader->unreadChar(CR); @@ -2755,14 +4275,14 @@ bool InStream::eoln() } } -void InStream::readEoln() -{ +void InStream::readEoln() { + lastLine = reader->getLine(); if (!eoln()) quit(_pe, "Expected EOLN"); } -void InStream::readEof() -{ +void InStream::readEof() { + lastLine = reader->getLine(); if (!eof()) quit(_pe, "Expected EOF"); @@ -2770,45 +4290,44 @@ void InStream::readEof() testlibFinalizeGuard.readEofCount++; } -bool InStream::seekEoln() -{ - if (NULL == file) +bool InStream::seekEoln() { + if (!strict && NULL == reader) return true; - + int cur; - do - { + do { cur = reader->nextChar(); - } - while (cur == SPACE || cur == TAB); + } while (cur == SPACE || cur == TAB); reader->unreadChar(cur); return eoln(); } -void InStream::nextLine() -{ +void InStream::nextLine() { readLine(); } -void InStream::readStringTo(std::string& result) -{ - if (NULL == file) +void InStream::readStringTo(std::string &result) { + if (NULL == reader) quit(_pe, "Expected line"); result.clear(); - int cur; - for (;;) - { - cur = reader->curChar(); + for (;;) { + int cur = reader->curChar(); - if (isEoln(cur)) + if (cur == LF || cur == EOFC) break; - if (cur == EOF) - break; + if (cur == CR) { + cur = reader->nextChar(); + if (reader->curChar() == LF) { + reader->unreadChar(cur); + break; + } + } + lastLine = reader->getLine(); result += char(reader->nextChar()); } @@ -2818,99 +4337,165 @@ void InStream::readStringTo(std::string& result) eoln(); } -std::string InStream::readString() -{ +std::string InStream::readString() { readStringTo(_tmpReadToken); return _tmpReadToken; } -void InStream::readStringTo(std::string& result, const pattern& p, const std::string& variableName) -{ +std::vector InStream::readStrings(int size, int indexBase) { + __testlib_readMany(readStrings, readString(), std::string, false) +} + +void InStream::readStringTo(std::string &result, const pattern &p, const std::string &variableName) { readStringTo(result); - if (!p.matches(result)) - { - if (variableName.empty()) - quit(_wa, ("Line \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); - else - quit(_wa, ("Line [name=" + variableName + "] equals to \"" + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + if (!p.matches(result)) { + if (readManyIteration == NO_INDEX) { + if (variableName.empty()) + quit(_wa, ("Line \"" + __testlib_part(result) + "\" doesn't correspond to pattern \"" + p.src() + + "\"").c_str()); + else + quit(_wa, ("Line [name=" + variableName + "] equals to \"" + __testlib_part(result) + + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + } else { + if (variableName.empty()) + quit(_wa, + ("Line element [index=" + vtos(readManyIteration) + "] equals to \"" + __testlib_part(result) + + "\" doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + else + quit(_wa, + ("Line element " + std::string(variableName) + "[" + vtos(readManyIteration) + "] equals to \"" + + __testlib_part(result) + "\", doesn't correspond to pattern \"" + p.src() + "\"").c_str()); + } } + if (strict && !variableName.empty()) + validator.addVariable(variableName); } -void InStream::readStringTo(std::string& result, const std::string& ptrn, const std::string& variableName) -{ +void InStream::readStringTo(std::string &result, const std::string &ptrn, const std::string &variableName) { readStringTo(result, pattern(ptrn), variableName); } -std::string InStream::readString(const pattern& p, const std::string& variableName) -{ +std::string InStream::readString(const pattern &p, const std::string &variableName) { readStringTo(_tmpReadToken, p, variableName); return _tmpReadToken; } -std::string InStream::readString(const std::string& ptrn, const std::string& variableName) -{ +std::vector +InStream::readStrings(int size, const pattern &p, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readStrings, readString(p, variablesName), std::string, false) +} + +std::string InStream::readString(const std::string &ptrn, const std::string &variableName) { readStringTo(_tmpReadToken, ptrn, variableName); return _tmpReadToken; } -void InStream::readLineTo(std::string& result) -{ +std::vector +InStream::readStrings(int size, const std::string &ptrn, const std::string &variablesName, int indexBase) { + pattern p(ptrn); + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readStrings, readString(p, variablesName), std::string, false) +} + +void InStream::readLineTo(std::string &result) { readStringTo(result); } -std::string InStream::readLine() -{ +std::string InStream::readLine() { return readString(); } -void InStream::readLineTo(std::string& result, const pattern& p, const std::string& variableName) -{ +std::vector InStream::readLines(int size, int indexBase) { + __testlib_readMany(readLines, readString(), std::string, false) +} + +void InStream::readLineTo(std::string &result, const pattern &p, const std::string &variableName) { readStringTo(result, p, variableName); } -void InStream::readLineTo(std::string& result, const std::string& ptrn, const std::string& variableName) -{ +void InStream::readLineTo(std::string &result, const std::string &ptrn, const std::string &variableName) { readStringTo(result, ptrn, variableName); } -std::string InStream::readLine(const pattern& p, const std::string& variableName) -{ +std::string InStream::readLine(const pattern &p, const std::string &variableName) { return readString(p, variableName); } -std::string InStream::readLine(const std::string& ptrn, const std::string& variableName) -{ +std::vector +InStream::readLines(int size, const pattern &p, const std::string &variablesName, int indexBase) { + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readLines, readString(p, variablesName), std::string, false) +} + +std::string InStream::readLine(const std::string &ptrn, const std::string &variableName) { return readString(ptrn, variableName); } -void InStream::close() -{ - if (opened) - fclose(file); - - if (NULL != reader) - { +std::vector +InStream::readLines(int size, const std::string &ptrn, const std::string &variablesName, int indexBase) { + pattern p(ptrn); + if (strict && !variablesName.empty()) + validator.addVariable(variablesName); + __testlib_readMany(readLines, readString(p, variablesName), std::string, false) +} + +#ifdef __GNUC__ +__attribute__ ((format (printf, 3, 4))) +#endif +void InStream::ensuref(bool cond, const char *format, ...) { + if (!cond) { + FMT_TO_RESULT(format, format, message); + this->__testlib_ensure(cond, message); + } +} + +void InStream::__testlib_ensure(bool cond, std::string message) { + if (!cond) + this->quit(_wa, message.c_str()); +} + +void InStream::close() { + if (NULL != reader) { + reader->close(); delete reader; reader = NULL; } - + opened = false; } -NORETURN void quit(TResult result, const std::string& msg) -{ +NORETURN void quit(TResult result, const std::string &msg) { ouf.quit(result, msg.c_str()); } -NORETURN void quit(TResult result, const char* msg) -{ +NORETURN void quit(TResult result, const char *msg) { ouf.quit(result, msg); } -NORETURN void __testlib_quitp(double points, const char* message) -{ - __testlib_points = points; - std::string stringPoints = removeDoubleTrailingZeroes(format("%.10f", points)); +double __testlib_preparePoints(double points_) { + volatile double points = points_; + if (__testlib_isNaN(points)) + quit(_fail, "Parameter 'points' can't be nan"); + if (__testlib_isInfinite(points)) + quit(_fail, "Parameter 'points' can't be infinite"); + if (points < -1E-8) + quit(_fail, "Parameter 'points' can't be negative"); + if (points <= 0.0) + points = +0.0; + if (points > 1E6 + 1E-8) + quit(_fail, "Parameter 'points' can't be greater than 1E6"); + if (points >= 1E6) + points = 1E6; + return points; +} + +NORETURN void __testlib_quitp(double points, const char *message) { + __testlib_points = __testlib_preparePoints(points); + std::string stringPoints = removeDoubleTrailingZeroes(testlib_format_("%.10f", __testlib_points)); std::string quitMessage; if (NULL == message || 0 == strlen(message)) @@ -2921,27 +4506,49 @@ NORETURN void __testlib_quitp(double points, const char* message) quit(_points, quitMessage.c_str()); } -NORETURN void quitp(float points, const std::string& message = "") -{ +NORETURN void __testlib_quitp(int points, const char *message) { + __testlib_points = __testlib_preparePoints(points); + std::string stringPoints = testlib_format_("%d", points); + + std::string quitMessage; + if (NULL == message || 0 == strlen(message)) + quitMessage = stringPoints; + else + quitMessage = stringPoints + " " + message; + + quit(_points, quitMessage.c_str()); +} + +NORETURN void quitp(float points, const std::string &message = "") { __testlib_quitp(double(points), message.c_str()); } -NORETURN void quitp(double points, const std::string& message = "") -{ +NORETURN void quitp(double points, const std::string &message = "") { __testlib_quitp(points, message.c_str()); } -NORETURN void quitp(long double points, const std::string& message = "") -{ +NORETURN void quitp(long double points, const std::string &message = "") { __testlib_quitp(double(points), message.c_str()); } +NORETURN void quitp(int points, const std::string &message = "") { + __testlib_quitp(points, message.c_str()); +} + +NORETURN void quitpi(const std::string &points_info, const std::string &message = "") { + if (points_info.find(' ') != std::string::npos) + quit(_fail, "Parameter 'points_info' can't contain spaces"); + if (message.empty()) + quit(_points, ("points_info=" + points_info).c_str()); + else + quit(_points, ("points_info=" + points_info + " " + message).c_str()); +} + template #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))) #endif -NORETURN void quitp(F points, const char* format, ...) -{ +NORETURN void quitp(F points, const char *format, ...) { FMT_TO_RESULT(format, format, message); quitp(points, message); } @@ -2949,8 +4556,7 @@ NORETURN void quitp(F points, const char* format, ...) #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))) #endif -NORETURN void quitf(TResult result, const char* format, ...) -{ +NORETURN void quitf(TResult result, const char *format, ...) { FMT_TO_RESULT(format, format, message); quit(result, message); } @@ -2958,39 +4564,35 @@ NORETURN void quitf(TResult result, const char* format, ...) #ifdef __GNUC__ __attribute__ ((format (printf, 3, 4))) #endif -void quitif(bool condition, TResult result, const char* format, ...) -{ - if (condition) - { +void quitif(bool condition, TResult result, const char *format, ...) { + if (condition) { FMT_TO_RESULT(format, format, message); quit(result, message); } } -NORETURN void __testlib_help() -{ +NORETURN void __testlib_help() { InStream::textColor(InStream::LightCyan); - std::fprintf(stderr, "TESTLIB %s, http://code.google.com/p/testlib/ ", VERSION); - std::fprintf(stderr, "by Mike Mirzayanov, copyright(c) 2005-2013\n"); + std::fprintf(stderr, "TESTLIB %s, https://github.com/MikeMirzayanov/testlib/ ", VERSION); + std::fprintf(stderr, "by Mike Mirzayanov, copyright(c) 2005-2020\n"); std::fprintf(stderr, "Checker name: \"%s\"\n", checkerName.c_str()); InStream::textColor(InStream::LightGray); std::fprintf(stderr, "\n"); std::fprintf(stderr, "Latest features: \n"); - for (size_t i = 0; i < sizeof(latestFeatures) / sizeof(char*); i++) - { + for (size_t i = 0; i < sizeof(latestFeatures) / sizeof(char *); i++) { std::fprintf(stderr, "*) %s\n", latestFeatures[i]); } std::fprintf(stderr, "\n"); std::fprintf(stderr, "Program must be run with the following arguments: \n"); - std::fprintf(stderr, " [ [<-appes>]]\n\n"); + std::fprintf(stderr, " [--testset testset] [--group group] [ [<-appes>]]\n\n"); + __testlib_exitCode = FAIL_EXIT_CODE; std::exit(FAIL_EXIT_CODE); } -static void __testlib_ensuresPreconditions() -{ +static void __testlib_ensuresPreconditions() { // testlib assumes: sizeof(int) = 4. __TESTLIB_STATIC_ASSERT(sizeof(int) == 4); @@ -3000,6 +4602,9 @@ static void __testlib_ensuresPreconditions() // testlib assumes: sizeof(long long) = 8. __TESTLIB_STATIC_ASSERT(sizeof(long long) == 8); + // testlib assumes: sizeof(double) = 8. + __TESTLIB_STATIC_ASSERT(sizeof(double) == 8); + // testlib assumes: no -ffast-math. if (!__testlib_isNaN(+__testlib_nan())) quit(_fail, "Function __testlib_isNaN is not working correctly: possible reason is '-ffast-math'"); @@ -3007,17 +4612,49 @@ static void __testlib_ensuresPreconditions() quit(_fail, "Function __testlib_isNaN is not working correctly: possible reason is '-ffast-math'"); } -void registerGen(int argc, char* argv[], int randomGeneratorVersion) -{ +std::string __testlib_testset; + +std::string getTestset() { + return __testlib_testset; +} + +std::string __testlib_group; + +std::string getGroup() { + return __testlib_group; +} + +static void __testlib_set_testset_and_group(int argc, char* argv[]) { + for (int i = 1; i < argc; i++) { + if (!strcmp("--testset", argv[i])) { + if (i + 1 < argc && strlen(argv[i + 1]) > 0) + __testlib_testset = argv[++i]; + else + quit(_fail, std::string("Expected non-empty testset after --testset command line parameter")); + } else if (!strcmp("--group", argv[i])) { + if (i + 1 < argc) + __testlib_group = argv[++i]; + else + quit(_fail, std::string("Expected group after --group command line parameter")); + } + } +} + +void registerGen(int argc, char *argv[], int randomGeneratorVersion) { if (randomGeneratorVersion < 0 || randomGeneratorVersion > 1) quitf(_fail, "Random generator version is expected to be 0 or 1."); random_t::version = randomGeneratorVersion; __testlib_ensuresPreconditions(); + TestlibFinalizeGuard::registered = true; testlibMode = _generator; __testlib_set_binary(stdin); rnd.setSeed(argc, argv); + +#if __cplusplus > 199711L || defined(_MSC_VER) + prepareOpts(argc, argv); +#endif } #ifdef USE_RND_AS_BEFORE_087 @@ -3026,148 +4663,308 @@ void registerGen(int argc, char* argv[]) registerGen(argc, argv, 0); } #else -void registerGen(int argc, char* argv[]) -{ +#ifdef __GNUC__ +#if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 4)) +__attribute__ ((deprecated("Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)." +" The third parameter stands for the random generator version." +" If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." +" Version 1 has been released on Spring, 2013. Use it to write new generators."))) +#else +__attribute__ ((deprecated)) +#endif +#endif +#ifdef _MSC_VER +__declspec(deprecated("Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)." + " The third parameter stands for the random generator version." + " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." + " Version 1 has been released on Spring, 2013. Use it to write new generators.")) +#endif +void registerGen(int argc, char *argv[]) { std::fprintf(stderr, "Use registerGen(argc, argv, 0) or registerGen(argc, argv, 1)." - " The third parameter stands for the random generator version." - " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." - " Version 1 has been released on Spring, 2013. Use it to write new generators.\n\n"); + " The third parameter stands for the random generator version." + " If you are trying to compile old generator use macro -DUSE_RND_AS_BEFORE_087 or registerGen(argc, argv, 0)." + " Version 1 has been released on Spring, 2013. Use it to write new generators.\n\n"); registerGen(argc, argv, 0); } #endif -void registerInteraction(int argc, char* argv[]) -{ +void setAppesModeEncoding(std::string appesModeEncoding) { + static const char* const ENCODINGS[] = {"ascii", "utf-7", "utf-8", "utf-16", "utf-16le", "utf-16be", "utf-32", "utf-32le", "utf-32be", "iso-8859-1", +"iso-8859-2", "iso-8859-3", "iso-8859-4", "iso-8859-5", "iso-8859-6", "iso-8859-7", "iso-8859-8", "iso-8859-9", "iso-8859-10", "iso-8859-11", +"iso-8859-13", "iso-8859-14", "iso-8859-15", "iso-8859-16", "windows-1250", "windows-1251", "windows-1252", "windows-1253", "windows-1254", "windows-1255", +"windows-1256", "windows-1257", "windows-1258", "gb2312", "gbk", "gb18030", "big5", "shift-jis", "euc-jp", "euc-kr", +"euc-cn", "euc-tw", "koi8-r", "koi8-u", "tis-620", "ibm437", "ibm850", "ibm852", "ibm855", "ibm857", +"ibm860", "ibm861", "ibm862", "ibm863", "ibm865", "ibm866", "ibm869", "macroman", "maccentraleurope", "maciceland", +"maccroatian", "macromania", "maccyrillic", "macukraine", "macgreek", "macturkish", "machebrew", "macarabic", "macthai", "hz-gb-2312", +"iso-2022-jp", "iso-2022-kr", "iso-2022-cn", "armscii-8", "tscii", "iscii", "viscii", "geostd8", "cp949", "cp874", +"cp1006", "cp775", "cp858", "cp737", "cp853", "cp856", "cp922", "cp1046", "cp1125", "cp1131", +"ptcp154", "koi8-t", "koi8-ru", "mulelao-1", "cp1133", "iso-ir-166", "tcvn", "iso-ir-14", "iso-ir-87", "iso-ir-159"}; + + appesModeEncoding = lowerCase(appesModeEncoding); + bool valid = false; + for (size_t i = 0; i < sizeof(ENCODINGS) / sizeof(ENCODINGS[0]); i++) + if (appesModeEncoding == ENCODINGS[i]) { + valid = true; + break; + } + if (!valid) + quit(_fail, "Unexpected encoding for setAppesModeEncoding(encoding)"); + ::appesModeEncoding = appesModeEncoding; +} + +void registerInteraction(int argc, char *argv[]) { __testlib_ensuresPreconditions(); + __testlib_set_testset_and_group(argc, argv); + TestlibFinalizeGuard::registered = true; testlibMode = _interactor; __testlib_set_binary(stdin); if (argc > 1 && !strcmp("--help", argv[1])) __testlib_help(); - - if (argc < 3 || argc > 6) - { + + if (argc < 3 || argc > 6) { quit(_fail, std::string("Program must be run with the following arguments: ") + - std::string(" [ [ [<-appes>]]]") + - "\nUse \"--help\" to get help information"); + std::string(" [ [ [<-appes>]]]") + + "\nUse \"--help\" to get help information"); } - if (argc <= 4) - { + if (argc <= 4) { resultName = ""; appesMode = false; } - if (argc == 5) - { +#ifndef EJUDGE + if (argc == 5) { resultName = argv[4]; appesMode = false; } - if (argc == 6) - { - if (strcmp("-APPES", argv[5]) && strcmp("-appes", argv[5])) - { + if (argc == 6) { + if (strcmp("-APPES", argv[5]) && strcmp("-appes", argv[5])) { quit(_fail, std::string("Program must be run with the following arguments: ") + " [ [<-appes>]]"); - } - else - { + } else { resultName = argv[4]; appesMode = true; } } +#endif inf.init(argv[1], _input); - if (strcmp(argv[2], "stdout") != 0) - { - tout.open(argv[2], std::ios_base::out); - if (tout.fail() || !tout.is_open()) - quit(_fail, std::string("Can not write to the test-output-file '") + argv[2] + std::string("'")); - } + if (strcmp(argv[2], "stdout") != 0 && strcmp(argv[2], "/dev/stdin") != 0) { + tout.open(argv[2], std::ios_base::out); + if (tout.fail() || !tout.is_open()) + quit(_fail, std::string("Can not write to the test-output-file '") + argv[2] + std::string("'")); + } ouf.init(stdin, _output); - + if (argc >= 4) ans.init(argv[3], _answer); else ans.name = "unopened answer stream"; } -void registerValidation() -{ +void registerValidation() { __testlib_ensuresPreconditions(); + TestlibFinalizeGuard::registered = true; testlibMode = _validator; + __testlib_set_binary(stdin); + __testlib_set_binary(stdout); + __testlib_set_binary(stderr); inf.init(stdin, _input); inf.strict = true; } -void registerTestlibCmd(int argc, char* argv[]) -{ +void registerValidation(int argc, char *argv[]) { + registerValidation(); + __testlib_set_testset_and_group(argc, argv); + + validator.initialize(); + TestlibFinalizeGuard::registered = true; + + std::string comment = "Validator must be run with the following arguments:" + " [--testset testset]" + " [--group group]" + " [--testOverviewLogFileName fileName]" + " [--testMarkupFileName fileName]" + " [--testCase testCase]" + " [--testCaseFileName fileName]" + ; + + for (int i = 1; i < argc; i++) { + if (!strcmp("--testset", argv[i])) { + if (i + 1 < argc && strlen(argv[i + 1]) > 0) + validator.setTestset(argv[++i]); + else + quit(_fail, comment); + } + if (!strcmp("--group", argv[i])) { + if (i + 1 < argc) + validator.setGroup(argv[++i]); + else + quit(_fail, comment); + } + if (!strcmp("--testOverviewLogFileName", argv[i])) { + if (i + 1 < argc) + validator.setTestOverviewLogFileName(argv[++i]); + else + quit(_fail, comment); + } + if (!strcmp("--testMarkupFileName", argv[i])) { + if (i + 1 < argc) + validator.setTestMarkupFileName(argv[++i]); + else + quit(_fail, comment); + } + if (!strcmp("--testCase", argv[i])) { + if (i + 1 < argc) { + long long testCase = stringToLongLong(inf, argv[++i]); + if (testCase < 1 || testCase >= __TESTLIB_MAX_TEST_CASE) + quit(_fail, testlib_format_("Argument testCase should be between 1 and %d, but ", __TESTLIB_MAX_TEST_CASE) + + toString(testCase) + " found"); + validator.setTestCase(int(testCase)); + } else + quit(_fail, comment); + } + if (!strcmp("--testCaseFileName", argv[i])) { + if (i + 1 < argc) { + validator.setTestCaseFileName(argv[++i]); + } else + quit(_fail, comment); + } + } +} + +void addFeature(const std::string &feature) { + if (testlibMode != _validator) + quit(_fail, "Features are supported in validators only."); + validator.addFeature(feature); +} + +void feature(const std::string &feature) { + if (testlibMode != _validator) + quit(_fail, "Features are supported in validators only."); + validator.feature(feature); +} + +class Checker { +private: + bool _initialized; + std::string _testset; + std::string _group; + +public: + Checker() : _initialized(false), _testset("tests"), _group() { + } + + void initialize() { + _initialized = true; + } + + std::string testset() const { + if (!_initialized) + __testlib_fail("Checker should be initialized with registerTestlibCmd(argc, argv) instead of registerTestlibCmd() to support checker.testset()"); + return _testset; + } + + std::string group() const { + if (!_initialized) + __testlib_fail("Checker should be initialized with registerTestlibCmd(argc, argv) instead of registerTestlibCmd() to support checker.group()"); + return _group; + } + + void setTestset(const char *const testset) { + _testset = testset; + } + + void setGroup(const char *const group) { + _group = group; + } +} checker; + +void registerTestlibCmd(int argc, char *argv[]) { __testlib_ensuresPreconditions(); + __testlib_set_testset_and_group(argc, argv); + TestlibFinalizeGuard::registered = true; testlibMode = _checker; - __testlib_set_binary(stdin); + __testlib_set_binary(stdin); - if (argc > 1 && !strcmp("--help", argv[1])) + std::vector args(1, argv[0]); + checker.initialize(); + + for (int i = 1; i < argc; i++) { + if (!strcmp("--testset", argv[i])) { + if (i + 1 < argc && strlen(argv[i + 1]) > 0) + checker.setTestset(argv[++i]); + else + quit(_fail, std::string("Expected testset after --testset command line parameter")); + } else if (!strcmp("--group", argv[i])) { + if (i + 1 < argc) + checker.setGroup(argv[++i]); + else + quit(_fail, std::string("Expected group after --group command line parameter")); + } else + args.push_back(argv[i]); + } + + argc = int(args.size()); + if (argc > 1 && "--help" == args[1]) __testlib_help(); - if (argc < 4 || argc > 6) - { + if (argc < 4 || argc > 6) { quit(_fail, std::string("Program must be run with the following arguments: ") + - std::string(" [ [<-appes>]]") + - "\nUse \"--help\" to get help information"); + std::string("[--testset testset] [--group group] [ [<-appes>]]") + + "\nUse \"--help\" to get help information"); } - if (argc == 4) - { + if (argc == 4) { resultName = ""; appesMode = false; } - if (argc == 5) - { - resultName = argv[4]; +#ifndef EJUDGE + if (argc == 5) { + resultName = args[4]; appesMode = false; } - if (argc == 6) - { - if (strcmp("-APPES", argv[5]) && strcmp("-appes", argv[5])) - { + if (argc == 6) { + if ("-APPES" != args[5] && "-appes" != args[5]) { quit(_fail, std::string("Program must be run with the following arguments: ") + " [ [<-appes>]]"); - } - else - { - resultName = argv[4]; + } else { + resultName = args[4]; appesMode = true; } } +#endif - inf.init(argv[1], _input); - ouf.init(argv[2], _output); - ans.init(argv[3], _answer); + inf.init(args[1], _input); + ouf.init(args[2], _output); + if (args[2] != std::string("/dev/stdin")) { + ouf.skipBom(); + } + ans.init(args[3], _answer); } -void registerTestlib(int argc, ...) -{ - if (argc < 3 || argc > 5) +void registerTestlib(int argc, ...) { + if (argc < 3 || argc > 5) quit(_fail, std::string("Program must be run with the following arguments: ") + - " [ [<-appes>]]"); + " [ [<-appes>]]"); + + char **argv = new char *[argc + 1]; - char** argv = new char*[argc + 1]; - va_list ap; va_start(ap, argc); argv[0] = NULL; - for (int i = 0; i < argc; i++) - { + for (int i = 0; i < argc; i++) { argv[i + 1] = va_arg(ap, char*); } va_end(ap); @@ -3176,113 +4973,55 @@ void registerTestlib(int argc, ...) delete[] argv; } -#ifdef __GNUC__ -__attribute__((const)) -#endif -inline bool doubleCompare(double expected, double result, double MAX_DOUBLE_ERROR) -{ - if (__testlib_isNaN(expected)) - { - return __testlib_isNaN(result); - } - else - if (__testlib_isInfinite(expected)) - { - if (expected > 0) - { - return result > 0 && __testlib_isInfinite(result); - } - else - { - return result < 0 && __testlib_isInfinite(result); - } - } - else - if (__testlib_isNaN(result) || __testlib_isInfinite(result)) - { - return false; - } - else - if (__testlib_abs(result - expected) <= MAX_DOUBLE_ERROR + 1E-15) - { - return true; - } - else - { - double minv = __testlib_min(expected * (1.0 - MAX_DOUBLE_ERROR), - expected * (1.0 + MAX_DOUBLE_ERROR)); - double maxv = __testlib_max(expected * (1.0 - MAX_DOUBLE_ERROR), - expected * (1.0 + MAX_DOUBLE_ERROR)); - return result + 1E-15 >= minv && result <= maxv + 1E-15; - } -} - -#ifdef __GNUC__ -__attribute__((const)) -#endif -inline double doubleDelta(double expected, double result) -{ - double absolute = __testlib_abs(result - expected); - - if (__testlib_abs(expected) > 1E-9) - { - double relative = __testlib_abs(absolute / expected); - return __testlib_min(absolute, relative); - } - else - return absolute; -} - -static inline void __testlib_ensure(bool cond, const std::string& msg) -{ +static inline void __testlib_ensure(bool cond, const std::string &msg) { if (!cond) quit(_fail, msg.c_str()); } -static inline void __testlib_ensure(bool cond, const char* msg) -{ +#ifdef __GNUC__ +__attribute__((unused)) +#endif +static inline void __testlib_ensure(bool cond, const char *msg) { if (!cond) quit(_fail, msg); } -#define ensure(cond) __testlib_ensure(cond, "Condition failed: \"" #cond "\"") +#define ensure(cond) __testlib_ensure((cond), "Condition failed: \"" #cond "\"") +#define STRINGIZE_DETAIL(x) (#x) +#define STRINGIZE(x) STRINGIZE_DETAIL((x)) +#define ensure_ext(cond) __testlib_ensure((cond), "Line " STRINGIZE(__LINE__) ": Condition failed: \"" #cond "\"") #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))) #endif -inline void ensuref(bool cond, const char* format, ...) -{ - if (!cond) - { +inline void ensuref(bool cond, const char *format, ...) { + if (!cond) { FMT_TO_RESULT(format, format, message); __testlib_ensure(cond, message); } } -NORETURN static void __testlib_fail(const std::string& message) -{ +NORETURN static void __testlib_fail(const std::string &message) { quitf(_fail, "%s", message.c_str()); } #ifdef __GNUC__ __attribute__ ((format (printf, 1, 2))) #endif -void setName(const char* format, ...) -{ +void setName(const char *format, ...) { FMT_TO_RESULT(format, format, name); checkerName = name; } -/* +/* * Do not use random_shuffle, because it will produce different result * for different C++ compilers. * * This implementation uses testlib random_t to produce random numbers, so * it is stable. - */ + */ template -void shuffle(_RandomAccessIter __first, _RandomAccessIter __last) -{ +void shuffle(_RandomAccessIter __first, _RandomAccessIter __last) { if (__first == __last) return; for (_RandomAccessIter __i = __first + 1; __i != __last; ++__i) std::iter_swap(__i, __first + rnd.next(int(__i - __first) + 1)); @@ -3290,8 +5029,10 @@ void shuffle(_RandomAccessIter __first, _RandomAccessIter __last) template -void random_shuffle(_RandomAccessIter , _RandomAccessIter ) -{ +#if defined(__GNUC__) && !defined(__clang__) +__attribute__ ((error("Don't use random_shuffle(), use shuffle() instead"))) +#endif +void random_shuffle(_RandomAccessIter, _RandomAccessIter) { quitf(_fail, "Don't use random_shuffle(), use shuffle() instead"); } @@ -3301,52 +5042,56 @@ void random_shuffle(_RandomAccessIter , _RandomAccessIter ) # define RAND_THROW_STATEMENT #endif +#if defined(__GNUC__) && !defined(__clang__) + +__attribute__ ((error("Don't use rand(), use rnd.next() instead"))) +#endif +#ifdef _MSC_VER +# pragma warning( disable : 4273 ) +#endif int rand() RAND_THROW_STATEMENT { quitf(_fail, "Don't use rand(), use rnd.next() instead"); - + /* This line never runs. */ //throw "Don't use rand(), use rnd.next() instead"; } +#if defined(__GNUC__) && !defined(__clang__) + +__attribute__ ((error("Don't use srand(), you should use " +"'registerGen(argc, argv, 1);' to initialize generator seed " +"by hash code of the command line params. The third parameter " +"is randomGeneratorVersion (currently the latest is 1)."))) +#endif +#ifdef _MSC_VER +# pragma warning( disable : 4273 ) +#endif void srand(unsigned int seed) RAND_THROW_STATEMENT { - quitf(_fail, "Don't use srand(), you should use " - "'registerGen(argc, argv, 1);' to initialize generator seed " - "by hash code of the command line params. The third parameter " - "is randomGeneratorVersion (currently the latest is 1) [ignored seed=%d].", seed); + quitf(_fail, "Don't use srand(), you should use " + "'registerGen(argc, argv, 1);' to initialize generator seed " + "by hash code of the command line params. The third parameter " + "is randomGeneratorVersion (currently the latest is 1) [ignored seed=%u].", seed); } -void startTest(int test) -{ +void startTest(int test) { const std::string testFileName = vtos(test); - if (NULL == freopen(testFileName.c_str(), "wt", stdout)) + if (NULL == testlib_freopen_(testFileName.c_str(), "wt", stdout)) __testlib_fail("Unable to write file '" + testFileName + "'"); } -inline std::string upperCase(std::string s) -{ - for (size_t i = 0; i < s.length(); i++) - if ('a' <= s[i] && s[i] <= 'z') - s[i] = char(s[i] - 'a' + 'A'); - return s; -} - -inline std::string lowerCase(std::string s) -{ - for (size_t i = 0; i < s.length(); i++) - if ('A' <= s[i] && s[i] <= 'Z') - s[i] = char(s[i] - 'A' + 'a'); - return s; -} - -inline std::string compress(const std::string& s) -{ +#ifdef __GNUC__ +__attribute__((const)) +#endif +inline std::string compress(const std::string &s) { return __testlib_part(s); } -inline std::string englishEnding(int x) -{ +#ifdef __GNUC__ +__attribute__((const)) +#endif +inline std::string englishEnding(int x) { x %= 100; if (x / 10 == 1) return "th"; @@ -3359,33 +5104,14 @@ inline std::string englishEnding(int x) return "th"; } -inline std::string trim(const std::string& s) -{ - if (s.empty()) - return s; - - int left = 0; - while (left < int(s.length()) && isBlanks(s[left])) - left++; - if (left >= int(s.length())) - return ""; - - int right = int(s.length()) - 1; - while (right >= 0 && isBlanks(s[right])) - right--; - if (right < 0) - return ""; - - return s.substr(left, right - left + 1); -} - -template -std::string join(_ForwardIterator first, _ForwardIterator last, _Separator separator) -{ +template +#ifdef __GNUC__ +__attribute__((const)) +#endif +std::string join(_ForwardIterator first, _ForwardIterator last, _Separator separator) { std::stringstream ss; bool repeated = false; - for (_ForwardIterator i = first; i != last; i++) - { + for (_ForwardIterator i = first; i != last; i++) { if (repeated) ss << separator; else @@ -3395,105 +5121,1212 @@ std::string join(_ForwardIterator first, _ForwardIterator last, _Separator separ return ss.str(); } -template -std::string join(_ForwardIterator first, _ForwardIterator last) -{ +template +#ifdef __GNUC__ +__attribute__((const)) +#endif +std::string join(_ForwardIterator first, _ForwardIterator last) { return join(first, last, ' '); } -template -std::string join(const _Collection& collection, _Separator separator) -{ +template +#ifdef __GNUC__ +__attribute__((const)) +#endif +std::string join(const _Collection &collection, _Separator separator) { return join(collection.begin(), collection.end(), separator); } -template -std::string join(const _Collection& collection) -{ +template +#ifdef __GNUC__ +__attribute__((const)) +#endif +std::string join(const _Collection &collection) { return join(collection, ' '); } -NORETURN void __testlib_expectedButFound(TResult result, std::string expected, std::string found, const char* prepend) -{ +/** + * Splits string s by character separator returning exactly k+1 items, + * where k is the number of separator occurrences. + */ +#ifdef __GNUC__ +__attribute__((const)) +#endif +std::vector split(const std::string &s, char separator) { + std::vector result; + std::string item; + for (size_t i = 0; i < s.length(); i++) + if (s[i] == separator) { + result.push_back(item); + item = ""; + } else + item += s[i]; + result.push_back(item); + return result; +} + +/** + * Splits string s by character separators returning exactly k+1 items, + * where k is the number of separator occurrences. + */ +#ifdef __GNUC__ +__attribute__((const)) +#endif +std::vector split(const std::string &s, const std::string &separators) { + if (separators.empty()) + return std::vector(1, s); + + std::vector isSeparator(256); + for (size_t i = 0; i < separators.size(); i++) + isSeparator[(unsigned char) (separators[i])] = true; + + std::vector result; + std::string item; + for (size_t i = 0; i < s.length(); i++) + if (isSeparator[(unsigned char) (s[i])]) { + result.push_back(item); + item = ""; + } else + item += s[i]; + result.push_back(item); + return result; +} + +/** + * Splits string s by character separator returning non-empty items. + */ +#ifdef __GNUC__ +__attribute__((const)) +#endif +std::vector tokenize(const std::string &s, char separator) { + std::vector result; + std::string item; + for (size_t i = 0; i < s.length(); i++) + if (s[i] == separator) { + if (!item.empty()) + result.push_back(item); + item = ""; + } else + item += s[i]; + if (!item.empty()) + result.push_back(item); + return result; +} + +/** + * Splits string s by character separators returning non-empty items. + */ +#ifdef __GNUC__ +__attribute__((const)) +#endif +std::vector tokenize(const std::string &s, const std::string &separators) { + if (separators.empty()) + return std::vector(1, s); + + std::vector isSeparator(256); + for (size_t i = 0; i < separators.size(); i++) + isSeparator[(unsigned char) (separators[i])] = true; + + std::vector result; + std::string item; + for (size_t i = 0; i < s.length(); i++) + if (isSeparator[(unsigned char) (s[i])]) { + if (!item.empty()) + result.push_back(item); + item = ""; + } else + item += s[i]; + + if (!item.empty()) + result.push_back(item); + + return result; +} + +NORETURN void __testlib_expectedButFound(TResult result, std::string expected, std::string found, const char *prepend) { std::string message; if (strlen(prepend) != 0) - message = format("%s: expected '%s', but found '%s'", - compress(prepend).c_str(), compress(expected).c_str(), compress(found).c_str()); + message = testlib_format_("%s: expected '%s', but found '%s'", + compress(prepend).c_str(), compress(expected).c_str(), compress(found).c_str()); else - message = format("expected '%s', but found '%s'", - compress(expected).c_str(), compress(found).c_str()); + message = testlib_format_("expected '%s', but found '%s'", + compress(expected).c_str(), compress(found).c_str()); quit(result, message); } -NORETURN void __testlib_expectedButFound(TResult result, double expected, double found, const char* prepend) -{ - std::string expectedString = removeDoubleTrailingZeroes(format("%.12f", expected)); - std::string foundString = removeDoubleTrailingZeroes(format("%.12f", found)); +NORETURN void __testlib_expectedButFound(TResult result, double expected, double found, const char *prepend) { + std::string expectedString = removeDoubleTrailingZeroes(testlib_format_("%.12f", expected)); + std::string foundString = removeDoubleTrailingZeroes(testlib_format_("%.12f", found)); __testlib_expectedButFound(result, expectedString, foundString, prepend); } -template +template #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, T expected, T found, const char* prependFormat = "", ...) -{ +NORETURN void expectedButFound(TResult result, T expected, T found, const char *prependFormat = "", ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); std::string expectedString = vtos(expected); std::string foundString = vtos(found); __testlib_expectedButFound(result, expectedString, foundString, prepend.c_str()); } -template <> +template<> #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, std::string expected, std::string found, const char* prependFormat, ...) -{ +NORETURN void +expectedButFound(TResult result, std::string expected, std::string found, const char *prependFormat, ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); __testlib_expectedButFound(result, expected, found, prepend.c_str()); } -template <> +template<> #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, double expected, double found, const char* prependFormat, ...) -{ +NORETURN void expectedButFound(TResult result, double expected, double found, const char *prependFormat, ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); - std::string expectedString = removeDoubleTrailingZeroes(format("%.12f", expected)); - std::string foundString = removeDoubleTrailingZeroes(format("%.12f", found)); + std::string expectedString = removeDoubleTrailingZeroes(testlib_format_("%.12f", expected)); + std::string foundString = removeDoubleTrailingZeroes(testlib_format_("%.12f", found)); __testlib_expectedButFound(result, expectedString, foundString, prepend.c_str()); } -template <> +template<> #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, const char* expected, const char* found, const char* prependFormat, ...) -{ +NORETURN void +expectedButFound(TResult result, const char *expected, const char *found, const char *prependFormat, + ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); __testlib_expectedButFound(result, std::string(expected), std::string(found), prepend.c_str()); } -template <> +template<> #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, float expected, float found, const char* prependFormat, ...) -{ +NORETURN void expectedButFound(TResult result, float expected, float found, const char *prependFormat, ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); __testlib_expectedButFound(result, double(expected), double(found), prepend.c_str()); } -template <> +template<> #ifdef __GNUC__ __attribute__ ((format (printf, 4, 5))) #endif -NORETURN void expectedButFound(TResult result, long double expected, long double found, const char* prependFormat, ...) -{ +NORETURN void +expectedButFound(TResult result, long double expected, long double found, const char *prependFormat, ...) { FMT_TO_RESULT(prependFormat, prependFormat, prepend); __testlib_expectedButFound(result, double(expected), double(found), prepend.c_str()); } +#if __cplusplus > 199711L || defined(_MSC_VER) +template +struct is_iterable { + template + static char test(typename U::iterator *x); + + template + static long test(U *x); + + static const bool value = sizeof(test(0)) == 1; +}; + +template +struct __testlib_enable_if { +}; + +template +struct __testlib_enable_if { + typedef T type; +}; + +template +typename __testlib_enable_if::value, void>::type __testlib_print_one(const T &t) { + std::cout << t; +} + +template +typename __testlib_enable_if::value, void>::type __testlib_print_one(const T &t) { + bool first = true; + for (typename T::const_iterator i = t.begin(); i != t.end(); i++) { + if (first) + first = false; + else + std::cout << " "; + std::cout << *i; + } +} + +template<> +typename __testlib_enable_if::value, void>::type +__testlib_print_one(const std::string &t) { + std::cout << t; +} + +template +void __println_range(A begin, B end) { + bool first = true; + for (B i = B(begin); i != end; i++) { + if (first) + first = false; + else + std::cout << " "; + __testlib_print_one(*i); + } + std::cout << std::endl; +} + +template +struct is_iterator { + static T makeT(); + + typedef void *twoptrs[2]; + + static twoptrs &test(...); + + template + static typename R::iterator_category *test(R); + + template + static void *test(R *); + + static const bool value = sizeof(test(makeT())) == sizeof(void *); +}; + +template +struct is_iterator::value>::type> { + static const bool value = false; +}; + +template +typename __testlib_enable_if::value, void>::type println(const A &a, const B &b) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << std::endl; +} + +template +typename __testlib_enable_if::value, void>::type println(const A &a, const B &b) { + __println_range(a, b); +} + +template +void println(const A *a, const A *b) { + __println_range(a, b); +} + +template<> +void println(const char *a, const char *b) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << std::endl; +} + +template +void println(const T &x) { + __testlib_print_one(x); + std::cout << std::endl; +} + +template +void println(const A &a, const B &b, const C &c) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << " "; + __testlib_print_one(c); + std::cout << std::endl; +} + +template +void println(const A &a, const B &b, const C &c, const D &d) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << " "; + __testlib_print_one(c); + std::cout << " "; + __testlib_print_one(d); + std::cout << std::endl; +} + +template +void println(const A &a, const B &b, const C &c, const D &d, const E &e) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << " "; + __testlib_print_one(c); + std::cout << " "; + __testlib_print_one(d); + std::cout << " "; + __testlib_print_one(e); + std::cout << std::endl; +} + +template +void println(const A &a, const B &b, const C &c, const D &d, const E &e, const F &f) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << " "; + __testlib_print_one(c); + std::cout << " "; + __testlib_print_one(d); + std::cout << " "; + __testlib_print_one(e); + std::cout << " "; + __testlib_print_one(f); + std::cout << std::endl; +} + +template +void println(const A &a, const B &b, const C &c, const D &d, const E &e, const F &f, const G &g) { + __testlib_print_one(a); + std::cout << " "; + __testlib_print_one(b); + std::cout << " "; + __testlib_print_one(c); + std::cout << " "; + __testlib_print_one(d); + std::cout << " "; + __testlib_print_one(e); + std::cout << " "; + __testlib_print_one(f); + std::cout << " "; + __testlib_print_one(g); + std::cout << std::endl; +} + +/* opts */ + +/** + * A struct for a singular testlib opt, containing the raw string value, + * and a boolean value for marking whether the opt is used. + */ +struct TestlibOpt { + std::string value; + bool used; + + TestlibOpt() : value(), used(false) {} +}; + +/** + * Get the type of opt based on the number of `-` at the beginning and the + * _validity_ of the key name. + * + * A valid key name must start with an alphabetical character. + * + * Returns: 1 if s has one `-` at the beginning, that is, "-keyName". + * 2 if s has two `-` at the beginning, that is, "--keyName". + * 0 otherwise. That is, if s has no `-` at the beginning, or has more + * than 2 at the beginning ("---keyName", "----keyName", ...), or the + * keyName is invalid (the first character is not an alphabetical + * character). + */ +size_t getOptType(char *s) { + if (!s || strlen(s) <= 1) + return 0; + + if (s[0] == '-') { + if (isalpha(s[1])) + return 1; + else if (s[1] == '-') + return isalpha(s[2]) ? 2 : 0; + } + + return 0; +} + +/** + * Parse the opt at a given index, and put it into the opts maps. + * + * An opt can has the following form: + * 1) -keyName=value or --keyName=value (ex. -n=10 --test-count=20) + * 2) -keyName value or --keyName value (ex. -n 10 --test-count 20) + * 3) -kNumval or --kNumval (ex. -n10 --t20) + * 4) -boolProperty or --boolProperty (ex. -sorted --tree-only) + * + * Only the second form consumes 2 arguments. The other consumes only 1 + * argument. + * + * In the third form, the key is a single character, and after the key is the + * value. The value _should_ be a number. + * + * In the forth form, the value is true. + * + * Params: + * - argc and argv: the number of command line arguments and the command line + * arguments themselves. + * - index: the starting index of the opts. + * - opts: the map containing the resulting opt. + * + * Returns: the number of consumed arguments to parse the opt. + * 0 if there is no arguments to parse. + * + * Algorithm details: + * TODO. Please refer to the implementation to see how the code handles the 3rd and 4th forms separately. + */ +size_t parseOpt(size_t argc, char *argv[], size_t index, std::map &opts) { + if (index >= argc) + return 0; + + size_t type = getOptType(argv[index]), inc = 1; + if (type > 0) { + std::string key(argv[index] + type), val; + size_t sep = key.find('='); + if (sep != std::string::npos) { + val = key.substr(sep + 1); + key = key.substr(0, sep); + } else { + if (index + 1 < argc && getOptType(argv[index + 1]) == 0) { + val = argv[index + 1]; + inc = 2; + } else { + if (key.length() > 1 && isdigit(key[1])) { + val = key.substr(1); + key = key.substr(0, 1); + } else { + val = "true"; + } + } + } + opts[key].value = val; + } else { + return inc; + } + + return inc; +} + +/** + * Global list containing all the arguments in the order given in the command line. + */ +std::vector __testlib_argv; + +/** + * Global dictionary containing all the parsed opts. + */ +std::map __testlib_opts; + +/** + * Whether automatic no unused opts ensurement should be done. This flag will + * be turned on when `has_opt` or `opt(key, default_value)` is called. + * + * The automatic ensurement can be suppressed when + * __testlib_ensureNoUnusedOptsSuppressed is true. + */ +bool __testlib_ensureNoUnusedOptsFlag = false; + +/** + * Suppress no unused opts automatic ensurement. Can be set to true with + * `suppressEnsureNoUnusedOpts()`. + */ +bool __testlib_ensureNoUnusedOptsSuppressed = false; + +/** + * Parse command line arguments into opts. + * The results are stored into __testlib_argv and __testlib_opts. + */ +void prepareOpts(int argc, char *argv[]) { + if (argc <= 0) + __testlib_fail("Opts: expected argc>=0 but found " + toString(argc)); + size_t n = static_cast(argc); // NOLINT(hicpp-use-auto,modernize-use-auto) + __testlib_opts = std::map(); + for (size_t index = 1; index < n; index += parseOpt(n, argv, index, __testlib_opts)); + __testlib_argv = std::vector(n); + for (size_t index = 0; index < n; index++) + __testlib_argv[index] = argv[index]; +} + +/** + * An utility function to get the argument with a given index. This function + * also print a readable message when no arguments are found. + */ +std::string __testlib_indexToArgv(int index) { + if (index < 0 || index >= int(__testlib_argv.size())) + __testlib_fail("Opts: index '" + toString(index) + "' is out of range [0," + + toString(__testlib_argv.size()) + ")"); + return __testlib_argv[size_t(index)]; +} + +/** + * An utility function to get the opt with a given key . This function + * also print a readable message when no opts are found. + */ +std::string __testlib_keyToOpts(const std::string &key) { + auto it = __testlib_opts.find(key); + if (it == __testlib_opts.end()) + __testlib_fail("Opts: unknown key '" + compress(key) + "'"); + it->second.used = true; + return it->second.value; +} + +template +T optValueToIntegral(const std::string &s, bool nonnegative); + +long double optValueToLongDouble(const std::string &s); + +std::string parseExponentialOptValue(const std::string &s) { + size_t pos = std::string::npos; + for (size_t i = 0; i < s.length(); i++) + if (s[i] == 'e' || s[i] == 'E') { + if (pos != std::string::npos) + __testlib_fail("Opts: expected typical exponential notation but '" + compress(s) + "' found"); + pos = i; + } + if (pos == std::string::npos) + return s; + std::string e = s.substr(pos + 1); + if (!e.empty() && e[0] == '+') + e = e.substr(1); + if (e.empty()) + __testlib_fail("Opts: expected typical exponential notation but '" + compress(s) + "' found"); + if (e.length() > 20) + __testlib_fail("Opts: expected typical exponential notation but '" + compress(s) + "' found"); + int ne = optValueToIntegral(e, false); + std::string num = s.substr(0, pos); + if (num.length() > 20) + __testlib_fail("Opts: expected typical exponential notation but '" + compress(s) + "' found"); + if (!num.empty() && num[0] == '+') + num = num.substr(1); + optValueToLongDouble(num); + bool minus = false; + if (num[0] == '-') { + minus = true; + num = num.substr(1); + } + for (int i = 0; i < +ne; i++) { + size_t sep = num.find('.'); + if (sep == std::string::npos) + num += '0'; + else { + if (sep + 1 == num.length()) + num[sep] = '0'; + else + std::swap(num[sep], num[sep + 1]); + } + } + for (int i = 0; i < -ne; i++) { + size_t sep = num.find('.'); + if (sep == std::string::npos) + num.insert(num.begin() + int(num.length()) - 1, '.'); + else { + if (sep == 0) + num.insert(num.begin() + 1, '0'); + else + std::swap(num[sep - 1], num[sep]); + } + } + while (!num.empty() && num[0] == '0') + num = num.substr(1); + while (num.find('.') != std::string::npos && num.back() == '0') + num = num.substr(0, num.length() - 1); + if (!num.empty() && num.back() == '.') + num = num.substr(0, num.length() - 1); + if ((!num.empty() && num[0] == '.') || num.empty()) + num.insert(num.begin(), '0'); + return (minus ? "-" : "") + num; +} + +template +T optValueToIntegral(const std::string &s_, bool nonnegative) { + std::string s(parseExponentialOptValue(s_)); + if (s.empty()) + __testlib_fail("Opts: expected integer but '" + compress(s_) + "' found"); + T value = 0; + long double about = 0.0; + signed char sign = +1; + size_t pos = 0; + if (s[pos] == '-') { + if (nonnegative) + __testlib_fail("Opts: expected non-negative integer but '" + compress(s_) + "' found"); + sign = -1; + pos++; + } + for (size_t i = pos; i < s.length(); i++) { + if (s[i] < '0' || s[i] > '9') + __testlib_fail("Opts: expected integer but '" + compress(s_) + "' found"); + value = T(value * 10 + s[i] - '0'); + about = about * 10 + s[i] - '0'; + } + value *= sign; + about *= sign; + if (fabsl(value - about) > 0.1) + __testlib_fail("Opts: integer overflow: expected integer but '" + compress(s_) + "' found"); + return value; +} + +long double optValueToLongDouble(const std::string &s_) { + std::string s(parseExponentialOptValue(s_)); + if (s.empty()) + __testlib_fail("Opts: expected float number but '" + compress(s_) + "' found"); + long double value = 0.0; + signed char sign = +1; + size_t pos = 0; + if (s[pos] == '-') { + sign = -1; + pos++; + } + bool period = false; + long double mul = 1.0; + for (size_t i = pos; i < s.length(); i++) { + if (s[i] == '.') { + if (period) + __testlib_fail("Opts: expected float number but '" + compress(s_) + "' found"); + else { + period = true; + continue; + } + } + if (period) + mul *= 10.0; + if (s[i] < '0' || s[i] > '9') + __testlib_fail("Opts: expected float number but '" + compress(s_) + "' found"); + if (period) + value += (s[i] - '0') / mul; + else + value = value * 10 + s[i] - '0'; + } + value *= sign; + return value; +} + +/** + * Return true if there is an opt with a given key. + * + * By calling this function, automatic ensurement for no unused opts will be + * done when the program is finalized. Call suppressEnsureNoUnusedOpts() to + * turn it off. + */ +bool has_opt(const std::string &key) { + __testlib_ensureNoUnusedOptsFlag = true; + return __testlib_opts.count(key) != 0; +} + +/* About the following part for opt with 2 and 3 arguments. + * + * To parse the argv/opts correctly for a give type (integer, floating point or + * string), some meta programming must be done to determine the type of + * the type, and use the correct parsing function accordingly. + * + * The pseudo algorithm for determining the type of T and parse it accordingly + * is as follows: + * + * if (T is integral type) { + * if (T is unsigned) { + * parse the argv/opt as an **unsigned integer** of type T. + * } else { + * parse the argv/opt as an **signed integer** of type T. + * } else { + * if (T is floating point type) { + * parse the argv/opt as an **floating point** of type T. + * } else { + * // T should be std::string + * just the raw content of the argv/opts. + * } + * } + * + * To help with meta programming, some `opt` function with 2 or 3 arguments are + * defined. + * + * Opt with 3 arguments: T opt(true/false is_integral, true/false is_unsigned, index/key) + * + * + The first argument is for determining whether the type T is an integral + * type. That is, the result of std::is_integral() should be passed to + * this argument. When false, the type _should_ be either floating point or a + * std::string. + * + * + The second argument is for determining whether the signedness of the type + * T (if it is unsigned or signed). That is, the result of + * std::is_unsigned() should be passed to this argument. This argument can + * be ignored if the first one is false, because it only applies to integer. + * + * Opt with 2 arguments: T opt(true/false is_floating_point, index/key) + * + The first argument is for determining whether the type T is a floating + * point type. That is, the result of std::is_floating_point() should be + * passed to this argument. When false, the type _should_ be a std::string. + */ + +template +T opt(std::false_type is_floating_point, int index); + +template<> +std::string opt(std::false_type /*is_floating_point*/, int index) { + return __testlib_indexToArgv(index); +} + +template +T opt(std::true_type /*is_floating_point*/, int index) { + return T(optValueToLongDouble(__testlib_indexToArgv(index))); +} + +template +T opt(std::false_type /*is_integral*/, U /*is_unsigned*/, int index) { + return opt(std::is_floating_point(), index); +} + +template +T opt(std::true_type /*is_integral*/, std::false_type /*is_unsigned*/, int index) { + return optValueToIntegral(__testlib_indexToArgv(index), false); +} + +template +T opt(std::true_type /*is_integral*/, std::true_type /*is_unsigned*/, int index) { + return optValueToIntegral(__testlib_indexToArgv(index), true); +} + +template<> +bool opt(std::true_type /*is_integral*/, std::true_type /*is_unsigned*/, int index) { + std::string value = __testlib_indexToArgv(index); + if (value == "true" || value == "1") + return true; + if (value == "false" || value == "0") + return false; + __testlib_fail("Opts: opt by index '" + toString(index) + "': expected bool true/false or 0/1 but '" + + compress(value) + "' found"); +} + +/** + * Return the parsed argv by a given index. + */ +template +T opt(int index) { + return opt(std::is_integral(), std::is_unsigned(), index); +} + +/** + * Return the raw string value of an argv by a given index. + */ +std::string opt(int index) { + return opt(index); +} + +/** + * Return the parsed argv by a given index. If the index is bigger than + * the number of argv, return the given default_value. + */ +template +T opt(int index, const T &default_value) { + if (index >= int(__testlib_argv.size())) { + return default_value; + } + return opt(index); +} + +/** + * Return the raw string value of an argv by a given index. If the index is + * bigger than the number of argv, return the given default_value. + */ +std::string opt(int index, const std::string &default_value) { + return opt(index, default_value); +} + +template +T opt(std::false_type is_floating_point, const std::string &key); + +template<> +std::string opt(std::false_type /*is_floating_point*/, const std::string &key) { + return __testlib_keyToOpts(key); +} + +template +T opt(std::true_type /*is_integral*/, const std::string &key) { + return T(optValueToLongDouble(__testlib_keyToOpts(key))); +} + +template +T opt(std::false_type /*is_integral*/, U, const std::string &key) { + return opt(std::is_floating_point(), key); +} + +template +T opt(std::true_type /*is_integral*/, std::false_type /*is_unsigned*/, const std::string &key) { + return optValueToIntegral(__testlib_keyToOpts(key), false); +} + +template +T opt(std::true_type /*is_integral*/, std::true_type /*is_unsigned*/, const std::string &key) { + return optValueToIntegral(__testlib_keyToOpts(key), true); +} + +template<> +bool opt(std::true_type /*is_integral*/, std::true_type /*is_unsigned*/, const std::string &key) { + if (!has_opt(key)) + return false; + std::string value = __testlib_keyToOpts(key); + if (value == "true" || value == "1") + return true; + if (value == "false" || value == "0") + return false; + __testlib_fail("Opts: key '" + compress(key) + "': expected bool true/false or 0/1 but '" + + compress(value) + "' found"); +} + +/** + * Return the parsed opt by a given key. + */ +template +T opt(const std::string &key) { + return opt(std::is_integral(), std::is_unsigned(), key); +} + +/** + * Return the raw string value of an opt by a given key + */ +std::string opt(const std::string &key) { + return opt(key); +} + +/* Scorer started. */ + +enum TestResultVerdict { + SKIPPED, + OK, + WRONG_ANSWER, + RUNTIME_ERROR, + TIME_LIMIT_EXCEEDED, + IDLENESS_LIMIT_EXCEEDED, + MEMORY_LIMIT_EXCEEDED, + COMPILATION_ERROR, + CRASHED, + FAILED +}; + +std::string serializeVerdict(TestResultVerdict verdict) { + switch (verdict) { + case SKIPPED: return "SKIPPED"; + case OK: return "OK"; + case WRONG_ANSWER: return "WRONG_ANSWER"; + case RUNTIME_ERROR: return "RUNTIME_ERROR"; + case TIME_LIMIT_EXCEEDED: return "TIME_LIMIT_EXCEEDED"; + case IDLENESS_LIMIT_EXCEEDED: return "IDLENESS_LIMIT_EXCEEDED"; + case MEMORY_LIMIT_EXCEEDED: return "MEMORY_LIMIT_EXCEEDED"; + case COMPILATION_ERROR: return "COMPILATION_ERROR"; + case CRASHED: return "CRASHED"; + case FAILED: return "FAILED"; + } + throw "Unexpected verdict"; +} + +TestResultVerdict deserializeTestResultVerdict(std::string s) { + if (s == "SKIPPED") + return SKIPPED; + else if (s == "OK") + return OK; + else if (s == "WRONG_ANSWER") + return WRONG_ANSWER; + else if (s == "RUNTIME_ERROR") + return RUNTIME_ERROR; + else if (s == "TIME_LIMIT_EXCEEDED") + return TIME_LIMIT_EXCEEDED; + else if (s == "IDLENESS_LIMIT_EXCEEDED") + return IDLENESS_LIMIT_EXCEEDED; + else if (s == "MEMORY_LIMIT_EXCEEDED") + return MEMORY_LIMIT_EXCEEDED; + else if (s == "COMPILATION_ERROR") + return COMPILATION_ERROR; + else if (s == "CRASHED") + return CRASHED; + else if (s == "FAILED") + return FAILED; + ensuref(false, "Unexpected serialized TestResultVerdict"); + // No return actually. + return FAILED; +} + +struct TestResult { + int testIndex; + std::string testset; + std::string group; + TestResultVerdict verdict; + double points; + long long timeConsumed; + long long memoryConsumed; + std::string input; + std::string output; + std::string answer; + int exitCode; + std::string checkerComment; +}; + +std::string serializePoints(double points) { + if (std::isnan(points)) + return ""; + else { + char c[64]; + snprintf(c, 64, "%.03lf", points); + return c; + } +} + +double deserializePoints(std::string s) { + if (s.empty()) + return std::numeric_limits::quiet_NaN(); + else { + double result; +#ifdef _MSC_VER + ensuref(sscanf_s(s.c_str(), "%lf", &result) == 1, "Invalid serialized points"); +#else + ensuref(std::sscanf(s.c_str(), "%lf", &result) == 1, "Invalid serialized points"); +#endif + return result; + } +} + +std::string escapeTestResultString(std::string s) { + std::string result; + for (size_t i = 0; i < s.length(); i++) { + if (s[i] == '\r') + continue; + if (s[i] == '\n') { + result += "\\n"; + continue; + } + if (s[i] == '\\' || s[i] == ';') + result += '\\'; + result += s[i]; + } + return result; +} + +std::string unescapeTestResultString(std::string s) { + std::string result; + for (size_t i = 0; i < s.length(); i++) { + if (s[i] == '\\' && i + 1 < s.length()) { + if (s[i + 1] == 'n') { + result += '\n'; + i++; + continue; + } else if (s[i + 1] == ';' || s[i + 1] == '\\') { + result += s[i + 1]; + i++; + continue; + } + } + result += s[i]; + } + return result; +} + +std::string serializeTestResult(TestResult tr) { + std::string result; + result += std::to_string(tr.testIndex); + result += ";"; + result += escapeTestResultString(tr.testset); + result += ";"; + result += escapeTestResultString(tr.group); + result += ";"; + result += serializeVerdict(tr.verdict); + result += ";"; + result += serializePoints(tr.points); + result += ";"; + result += std::to_string(tr.timeConsumed); + result += ";"; + result += std::to_string(tr.memoryConsumed); + result += ";"; + result += escapeTestResultString(tr.input); + result += ";"; + result += escapeTestResultString(tr.output); + result += ";"; + result += escapeTestResultString(tr.answer); + result += ";"; + result += std::to_string(tr.exitCode); + result += ";"; + result += escapeTestResultString(tr.checkerComment); + return result; +} + +TestResult deserializeTestResult(std::string s) { + std::vector items; + std::string t; + for (size_t i = 0; i < s.length(); i++) { + if (s[i] == '\\') { + t += s[i]; + if (i + 1 < s.length()) + t += s[i + 1]; + i++; + continue; + } else { + if (s[i] == ';') { + items.push_back(t); + t = ""; + } else + t += s[i]; + } + } + items.push_back(t); + + ensuref(items.size() == 12, "Invalid TestResult serialization: expected exactly 12 items"); + + TestResult tr; + size_t pos = 0; + tr.testIndex = stoi(items[pos++]); + tr.testset = unescapeTestResultString(items[pos++]); + tr.group = unescapeTestResultString(items[pos++]); + tr.verdict = deserializeTestResultVerdict(items[pos++]); + tr.points = deserializePoints(items[pos++]); + tr.timeConsumed = stoll(items[pos++]); + tr.memoryConsumed = stoll(items[pos++]); + tr.input = unescapeTestResultString(items[pos++]); + tr.output = unescapeTestResultString(items[pos++]); + tr.answer = unescapeTestResultString(items[pos++]); + tr.exitCode = stoi(items[pos++]); + tr.checkerComment = unescapeTestResultString(items[pos++]); + + return tr; +} + +std::vector readTestResults(std::string fileName) { + std::ifstream stream; + stream.open(fileName.c_str(), std::ios::in); + ensuref(stream.is_open(), "Can't read test results file '%s'", fileName.c_str()); + std::vector result; + std::string line; + while (getline(stream, line)) + if (!line.empty()) + result.push_back(deserializeTestResult(line)); + stream.close(); + return result; +} + +std::function)> __testlib_scorer; + +struct TestlibScorerGuard { + ~TestlibScorerGuard() { + if (testlibMode == _scorer) { + std::vector testResults; + while (!inf.eof()) { + std::string line = inf.readLine(); + if (!line.empty()) + testResults.push_back(deserializeTestResult(line)); + } + inf.readEof(); + printf("%.3f\n", __testlib_scorer(testResults)); + } + } +} __testlib_scorer_guard; + +void registerScorer(int argc, char *argv[], std::function)> scorer) { + /* Suppress unused. */ + (void)(argc), (void)(argv); + + __testlib_ensuresPreconditions(); + + testlibMode = _scorer; + __testlib_set_binary(stdin); + + inf.init(stdin, _input); + inf.strict = false; + + __testlib_scorer = scorer; +} + +/* Scorer ended. */ + +/** + * Return the parsed opt by a given key. If no opts with the given key are + * found, return the given default_value. + * + * By calling this function, automatic ensurement for no unused opts will be + * done when the program is finalized. Call suppressEnsureNoUnusedOpts() to + * turn it off. + */ +template +T opt(const std::string &key, const T &default_value) { + if (!has_opt(key)) { + return default_value; + } + return opt(key); +} + +/** + * Return the raw string value of an opt by a given key. If no opts with the + * given key are found, return the given default_value. + * + * By calling this function, automatic ensurement for no unused opts will be + * done when the program is finalized. Call suppressEnsureNoUnusedOpts() to + * turn it off. + */ +std::string opt(const std::string &key, const std::string &default_value) { + return opt(key, default_value); +} + +/** + * Check if all opts are used. If not, __testlib_fail is called. + * Should be used after calling all opt() function calls. + * + * This function is useful when opt() with default_value for checking typos + * in the opt's key. + */ +void ensureNoUnusedOpts() { + for (const auto &opt: __testlib_opts) { + if (!opt.second.used) { + __testlib_fail(testlib_format_("Opts: unused key '%s'", compress(opt.first).c_str())); + } + } +} + +void suppressEnsureNoUnusedOpts() { + __testlib_ensureNoUnusedOptsSuppressed = true; +} + +void TestlibFinalizeGuard::autoEnsureNoUnusedOpts() { + if (__testlib_ensureNoUnusedOptsFlag && !__testlib_ensureNoUnusedOptsSuppressed) { + ensureNoUnusedOpts(); + } +} + +TestlibFinalizeGuard testlibFinalizeGuard; +#endif + +#ifdef __GNUC__ +__attribute__ ((format (printf, 1, 2))) +#endif +std::string testlib_format_(const char *fmt, ...) { + FMT_TO_RESULT(fmt, fmt, result); + return result; +} + +std::string testlib_format_(const std::string fmt, ...) { + FMT_TO_RESULT(fmt, fmt.c_str(), result); + return result; +} + +#if (__cplusplus >= 202002L && __has_include()) || __cpp_lib_format +template +std::string format(const char* fmt, Args&&... args) { + size_t size = size_t(std::snprintf(nullptr, 0, fmt, args...) + 1); + std::vector buffer(size); + std::snprintf(buffer.data(), size, fmt, args...); + return std::string(buffer.data()); +} + +template +std::string format(const std::string fmt, Args&&... args) { + size_t size = size_t(std::snprintf(nullptr, 0, fmt.c_str(), args...) + 1); + std::vector buffer(size); + std::snprintf(buffer.data(), size, fmt.c_str(), args...); + return std::string(buffer.data()); +} +#else +#ifdef __GNUC__ +__attribute__ ((format (printf, 1, 2))) +#endif +std::string format(const char *fmt, ...) { + FMT_TO_RESULT(fmt, fmt, result); + return result; +} + +std::string format(const std::string fmt, ...) { + FMT_TO_RESULT(fmt, fmt.c_str(), result); + return result; +} +#endif + #endif diff --git a/judger/uoj_judger/include/uoj_env.h b/judger/uoj_judger/include/uoj_env.h deleted file mode 100644 index 11f966130..000000000 --- a/judger/uoj_judger/include/uoj_env.h +++ /dev/null @@ -1,38 +0,0 @@ -#include "uoj_work_path.h" - -#define UOJ_DATA_PATH UOJ_WORK_PATH "/data" -#define UOJ_RESULT_PATH UOJ_WORK_PATH "/result" - -#define RS_SPJ_BASE 1000 -#define failed_spj RS_SPJ_BASE -#define successed_hack 100000 -#define RS_SPJ RS_SPJ_BASE -#define RS_HACK successed_hack -#define RS_AC 0 -#define RS_WA 1 -#define RS_RE 2 -#define RS_MLE 3 -#define RS_TLE 4 -#define RS_OLE 5 -#define RS_DGS 6 -#define RS_JGF 7 -#define RS_SPJ_AC (RS_SPJ + RS_AC) -#define RS_SPJ_RE (RS_SPJ_BASE + RS_RE) -#define RS_SPJ_MLE (RS_SPJ_BASE + RS_MLE) -#define RS_SPJ_TLE (RS_SPJ_BASE + RS_TLE) -#define RS_SPJ_OLE (RS_SPJ_BASE + RS_OLE) -#define RS_SPJ_DGS (RS_SPJ_BASE + RS_DGS) -#define RS_SPJ_JGF (RS_SPJ_BASE + RS_JGF) -#define RS_HK_RE (successed_hack + RS_RE) -#define RS_HK_MLE (successed_hack + RS_MLE) -#define RS_HK_TLE (successed_hack + RS_TLE) -#define RS_HK_OLE (successed_hack + RS_OLE) -#define RS_HK_DGS (successed_hack + RS_DGS) -#define RS_HK_JGF (successed_hack + RS_JGF) -#define RS_HK_SPJ_RE (successed_hack + RS_SPJ_RE) -#define RS_HK_SPJ_MLE (successed_hack + RS_SPJ_MLE) -#define RS_HK_SPJ_TLE (successed_hack + RS_SPJ_TLE) -#define RS_HK_SPJ_OLE (successed_hack + RS_SPJ_OLE) -#define RS_HK_SPJ_DGS (successed_hack + RS_SPJ_DGS) -#define RS_HK_SPJ_JGF (successed_hack + RS_SPJ_JGF) - diff --git a/judger/uoj_judger/include/uoj_judger.h b/judger/uoj_judger/include/uoj_judger.h index ce03fa198..ebf6a847b 100644 --- a/judger/uoj_judger/include/uoj_judger.h +++ b/judger/uoj_judger/include/uoj_judger.h @@ -1,287 +1,711 @@ -#include +#pragma once + +#include +#include +#include +#include + #include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include #include -#include #include +#include +#include +#include +#include +#include #include -#include -#include -#include +#include -#include -#include -#include +#include "uoj_run.h" +#include "uoj_secure.h" -#include "uoj_env.h" using namespace std; -/*========================== execute ====================== */ +/*========================== string ====================== */ -string escapeshellarg(const string &arg) { - string res = "'"; - for (size_t i = 0; i < arg.size(); i++) { - if (arg[i] == '\'') { - res += "'\\''"; - } else { - res += arg[i]; - } - } - res += "'"; - return res; +template +inline string vtos(const T &v) { + ostringstream sout; + sout << v; + return sout.str(); } -string realpath(const string &path) { - char real[PATH_MAX + 1]; - if (realpath(path.c_str(), real) == NULL) { - return ""; - } - return real; +inline string htmlspecialchars(const string &s) { + string r; + for (int i = 0; i < (int)s.length(); i++) { + switch (s[i]) { + case '&': + r += "&"; + break; + case '<': + r += "<"; + break; + case '>': + r += ">"; + break; + case '"': + r += """; + break; + case '\0': + r += "\\0"; + break; + default: + r += s[i]; + break; + } + } + return r; +} + +// trim from start (in place) +inline void ltrim(std::string &s) { + s.erase(s.begin(), + std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); })); +} + +// trim from end (in place) +inline void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }) + .base(), + s.end()); +} + +// trim from both ends (in place) +inline void trim(std::string &s) { + rtrim(s); + ltrim(s); +} + +// trim from start (copying) +inline std::string ltrim_copy(std::string s) { + ltrim(s); + return s; +} + +// trim from end (copying) +inline std::string rtrim_copy(std::string s) { + rtrim(s); + return s; +} + +// trim from both ends (copying) +inline std::string trim_copy(std::string s) { + trim(s); + return s; +} + +/*========================== random ====================== */ + +inline string gen_token() { + u64 seed = time(NULL); + FILE *f = fopen("/dev/urandom", "r"); + if (f) { + for (int i = 0; i < 8; i++) seed = seed << 8 | (u8)fgetc(f); + fclose(f); + } + uoj_mt_rand_engine rnd(seed); + return rnd.randstr(64); +} + +/*========================== crypto ====================== */ + +inline string file_get_contents(const string &name) { + string out; + FILE *f = fopen(name.c_str(), "r"); + if (!f) { + return out; + } + + const int BUFFER_SIZE = 1024; + u8 buffer[BUFFER_SIZE + 1]; + while (!feof(f)) { + int ret = fread(buffer, 1, BUFFER_SIZE, f); + if (ret < 0) { + break; + } + out.append((char *)buffer, ret); + } + fclose(f); + return out; +} +inline bool file_put_contents(const string &name, const string &m) { + FILE *f = fopen(name.c_str(), "w"); + if (!f) { + return false; + } + int c = fwrite(m.data(), 1, m.length(), f); + fclose(f); + return c == (int)m.length(); +} + +inline bool file_encrypt(const string &fi, const string &fo, const string &key) { + string m = file_get_contents(fi); + uoj_cipher cipher(key); + cipher.encrypt(m); + return file_put_contents(fo, m); +} +inline bool file_decrypt(const string &fi, const string &fo, const string &key) { + string m = file_get_contents(fi); + uoj_cipher cipher(key); + if (cipher.decrypt(m)) { + file_put_contents(fo, m); + return true; + } else { + file_put_contents(fo, "Unauthorized output"); + return false; + } } +/*========================== execute ====================== */ -int execute(const char *cmd) { - return system(cmd); -} - -int executef(const char *fmt, ...) { - const int MaxL = 512; - char cmd[MaxL]; - va_list ap; - va_start(ap, fmt); - int res = vsnprintf(cmd, MaxL, fmt, ap); - if (res < 0 || res >= MaxL) { - return -1; - } - res = execute(cmd); - va_end(ap); - return res; +string realpath(const string &path) { + char real[PATH_MAX + 1]; + if (realpath(path.c_str(), real) == NULL) { + return ""; + } + return real; } /*======================== execute End ==================== */ /*========================= file ====================== */ -string file_preview(const string &name, const size_t &len = 100) { - std::wifstream f(name); - if (!f) - return ""; - f.imbue(std::locale("C.UTF-8")); - - std::vector buf(len + 5, 0); - f.read(&buf[0], len + 4); - - auto it = std::find(buf.begin(), buf.end(), 0); - if (it - buf.begin() > len + 3) { - buf.resize(len); - for (wchar_t c: "...") - buf.push_back(c); - } - - std::wstring_convert, wchar_t> cv; - return cv.to_bytes(&buf[0]); +string file_preview(const string &name, const int &len = 100) { + FILE *f = fopen(name.c_str(), "r"); + if (f == NULL) { + return ""; + } + + struct stat stat_buf; + stat(name.c_str(), &stat_buf); + + string res = ""; + if (len == -1) { + int c; + while (c = fgetc(f), c != EOF) { + res += c; + } + } else { + int c; + while (c = fgetc(f), c != EOF && (int)res.size() < len + 4) { + res += c; + } + if ((int)res.size() > len + 3) { + int omitted = (int)stat_buf.st_size - len; + res.resize(len); + res += "\n\n(" + to_string(omitted) + " bytes omitted)"; + } + } + fclose(f); + return res; +} +int file_size(const string &name) { + struct stat st; + stat(name.c_str(), &st); + return (int)st.st_size; } - void file_hide_token(const string &name, const string &token) { - executef("cp %s %s.bak", name.c_str(), name.c_str()); - - FILE *rf = fopen((name + ".bak").c_str(), "r"); - FILE *wf = fopen(name.c_str(), "w"); - int c; - for (int i = 0; i <= (int)token.length(); i++) - { - c = fgetc(rf); - if (c != (i < (int)token.length() ? token[i] : '\n')) - { - fprintf(wf, "Unauthorized output\n"); - fclose(rf); - fclose(wf); - return; - } - } - while (c = fgetc(rf), c != EOF) { - fputc(c, wf); - } - fclose(rf); - fclose(wf); + executef("cp %s %s.bak", name.c_str(), name.c_str()); + + FILE *rf = fopen((name + ".bak").c_str(), "r"); + FILE *wf = fopen(name.c_str(), "w"); + int c; + for (int i = 0; i <= (int)token.length(); i++) { + c = fgetc(rf); + if (c != (i < (int)token.length() ? token[i] : '\n')) { + fprintf(wf, "Unauthorized output\n"); + fclose(rf); + fclose(wf); + return; + } + } + while (c = fgetc(rf), c != EOF) { + fputc(c, wf); + } + fclose(rf); + fclose(wf); +} +void file_replace_tokens(const string &name, const string &token, const string &new_token) { + string esc_name = escapeshellarg(name); + string esc_token = escapeshellarg(token); + string esc_new_token = escapeshellarg(new_token); + executef("sed -i s/%s/%s/g %s", esc_token.c_str(), esc_new_token.c_str(), esc_name.c_str()); +} +void file_copy(const string &a, const string &b) { // copy a to b + string esc_a = escapeshellarg(a); + string esc_b = escapeshellarg(b); + executef("cp %s %s -f", esc_a.c_str(), + esc_b.c_str()); // the most cubao implementation in the world +} +void file_move(const string &a, const string &b) { // move a to b + string esc_a = escapeshellarg(a); + string esc_b = escapeshellarg(b); + executef("mv %s %s", esc_a.c_str(), + esc_b.c_str()); // the most cubao implementation in the world } /*======================= file End ==================== */ /*====================== parameter ==================== */ -struct RunLimit { - int time; - int real_time; - int memory; - int output; - - RunLimit() { - } - RunLimit(const int &_time, const int &_memory, const int &_output) - : time(_time), memory(_memory), output(_output), real_time(-1) { - } -}; +typedef runp::limits_t RunLimit; +typedef runp::result RunResult; const RunLimit RL_DEFAULT = RunLimit(1, 256, 64); -const RunLimit RL_JUDGER_DEFAULT = RunLimit(600, 1024, 128); +const RunLimit RL_GENERATOR_DEFAULT = RunLimit(2, 512, 64); +const RunLimit RL_JUDGER_DEFAULT = RunLimit(600, 2048, 128); // 2048 = 2GB. change it if needed const RunLimit RL_CHECKER_DEFAULT = RunLimit(5, 256, 64); const RunLimit RL_INTERACTOR_DEFAULT = RunLimit(1, 256, 64); const RunLimit RL_VALIDATOR_DEFAULT = RunLimit(5, 256, 64); -const RunLimit RL_MARKER_DEFAULT = RunLimit(5, 256, 64); -const RunLimit RL_COMPILER_DEFAULT = RunLimit(15, 512, 64); - -struct PointInfo { - int num; - int scr; - int ust, usm; - string info, in, out, res; - - PointInfo(const int &_num, const int &_scr, - const int &_ust, const int &_usm, const string &_info, - const string &_in, const string &_out, const string &_res) - : num(_num), scr(_scr), - ust(_ust), usm(_usm), info(_info), - in(_in), out(_out), res(_res) { - if (info == "default") { - if (scr == 0) { - info = "Wrong Answer"; - } else if (scr == 100) { - info = "Accepted"; - } else { - info = "Acceptable Answer"; - } - } - } +const RunLimit RL_COMPILER_DEFAULT = RunLimit(15, 2048, 64); + +struct InfoBlock { + string title; + string content; + int orig_size; + + static InfoBlock empty(const string &title) { + InfoBlock info; + info.title = title; + info.content = ""; + info.orig_size = -1; + return info; + } + static InfoBlock from_string(const string &title, const string &content) { + InfoBlock info; + info.title = title; + info.content = content; + info.orig_size = -1; + return info; + } + + static InfoBlock from_file(const string &title, const string &name) { + InfoBlock info; + info.title = title; + info.content = file_preview(name); + info.orig_size = -1; + return info; + } + static InfoBlock from_file_with_size(const string &title, const string &name) { + InfoBlock info; + info.title = title; + info.content = file_preview(name); + info.orig_size = file_size(name); + return info; + } + + string to_str() const { + if (title == "in") { + return "" + htmlspecialchars(content) + ""; + } + if (title == "out") { + return "" + htmlspecialchars(content) + ""; + } + if (title == "res") { + return "" + htmlspecialchars(content) + ""; + } + + string str = ""; + + return str; + } }; -struct CustomTestInfo { - int ust, usm; - string info, exp, out; +enum SCORE_MODE { SM_INT, SM_REAL }; + +/** + * @brief the value type for storing the score of a point/subtask/submission + */ +class score_t { +private: + double __scr; + +public: + static SCORE_MODE mode; + static int P; + static int D; + + score_t() = default; + score_t(const double &_scr) : __scr(_scr) {} + +#define SCORE_OP(x) \ + friend inline score_t operator x(const score_t &lhs, const score_t &rhs) { \ + return lhs.__scr x rhs.__scr; \ + } + SCORE_OP(+) + SCORE_OP(-) + SCORE_OP(*) + SCORE_OP(/) +#undef SCORE_OP + +#define SCORE_OP(x) \ + friend inline bool operator x(const score_t &lhs, const score_t &rhs) { \ + return lhs.__scr x rhs.__scr; \ + } + SCORE_OP(==) + SCORE_OP(!=) + SCORE_OP(<) + SCORE_OP(<=) + SCORE_OP(>) + SCORE_OP(>=) +#undef SCORE_OP + +#define SCORE_OP(x) \ + inline score_t &operator x(const score_t & rhs) { \ + __scr x rhs.__scr; \ + return *this; \ + } + SCORE_OP(+=) + SCORE_OP(-=) +#undef SCORE_OP + +#define SCORE_OP(x) \ + inline score_t operator x() { \ + return x __scr; \ + } + SCORE_OP(-) + SCORE_OP(+) +#undef SCORE_OP + + inline score_t rounded_score() const { + if (mode == SM_REAL) { + return round(__scr * P) / P; + } else { + // score_type = int. round to integer + return round(__scr); + } + } + + explicit inline operator int() const { + return (int)round(__scr); + } + + explicit inline operator double() const { + return __scr; + } + + friend inline ostream &operator<<(ostream &out, const score_t &scr) { + auto default_prec = out.precision(); + out.precision(13); + out << scr.__scr; + out.precision(default_prec); + return out; + } +}; + +SCORE_MODE score_t::mode; +int score_t::P; +int score_t::D; + +/** + * @brief given a score in a system where the full mark is 100, scale it so that the full mark + * equals "full" + */ +score_t scale_score(score_t scr100, score_t full) { + if (score_t::mode == SM_REAL) { + return (scr100 / 100 * full).rounded_score(); + } else { + // score_type = int. round scr100 and full to integers. do integer division + return int(scr100) * int(full) / 100; + } +} + +struct PointInfo { + static bool show_in; + static bool show_out; + static bool show_res; + + int num; + score_t scr; + int ust, usm; + string info, in, out, res; + + bool use_li; + vector li; + + PointInfo(const int &_num, const score_t &_scr, const int &_ust, const int &_usm, + const string &_info = "default") : + num(_num), scr(_scr), ust(_ust), usm(_usm), info(_info) { + use_li = true; + if (info == "default") { + if (scr == 0) { + info = "Wrong Answer"; + } else if (scr == 100) { + info = "Accepted"; + } else { + info = "Acceptable Answer"; + } + } + } + + PointInfo(const int &_num, const score_t &_scr, const int &_ust, const int &_usm, + const string &_info, const string &_in, const string &_out, const string &_res) : + num(_num), scr(_scr), ust(_ust), usm(_usm), info(_info), in(_in), out(_out), res(_res) { + use_li = false; + if (info == "default") { + if (scr == 0) { + info = "Wrong Answer"; + } else if (scr == 100) { + info = "Accepted"; + } else { + info = "Acceptable Answer"; + } + } + } + + friend inline ostream &operator<<(ostream &out, const PointInfo &info) { + out << "" << endl; + if (!info.use_li) { + if (PointInfo::show_in) { + out << "" << htmlspecialchars(info.in) << "" << endl; + } + if (PointInfo::show_out) { + out << "" << htmlspecialchars(info.out) << "" << endl; + } + if (PointInfo::show_res) { + out << "" << htmlspecialchars(info.res) << "" << endl; + } + } else { + for (const auto &b : info.li) { + if (b.title == "in" && !PointInfo::show_in) { + continue; + } + if (b.title == "out" && !PointInfo::show_out) { + continue; + } + if (b.title == "res" && !PointInfo::show_res) { + continue; + } + out << b.to_str() << endl; + } + } + out << "" << endl; + return out; + } +}; + +bool PointInfo::show_in = true; +bool PointInfo::show_out = true; +bool PointInfo::show_res = true; + +struct CustomTestInfo { + int ust, usm; + string info, exp, out; + + CustomTestInfo(const int &_ust, const int &_usm, const string &_info, const string &_exp, + const string &_out) : + ust(_ust), usm(_usm), info(_info), exp(_exp), out(_out) {} +}; - CustomTestInfo(const int &_ust, const int &_usm, const string &_info, - const string &_exp, const string &_out) - : ust(_ust), usm(_usm), info(_info), - exp(_exp), out(_out) { - } +struct SubtaskMetaInfo { + int num; + vector points_id; + string subtask_type; + string subtask_used_time_type; + vector subtask_dependencies; + score_t full_score; + + inline bool is_ordinary() { + return subtask_type == "packed" && subtask_used_time_type == "sum"; + } }; -struct RunResult { - int type; - int ust, usm; - int exit_code; - - static RunResult failed_result() { - RunResult res; - res.type = RS_JGF; - res.ust = -1; - res.usm = -1; - return res; - } - - static RunResult from_file(const string &file_name) { - RunResult res; - FILE *fres = fopen(file_name.c_str(), "r"); - if (fres == NULL || fscanf(fres, "%d %d %d %d", &res.type, &res.ust, &res.usm, &res.exit_code) != 4) { - return RunResult::failed_result(); - } - fclose(fres); - return res; - } +struct SubtaskInfo { + SubtaskMetaInfo meta; + + bool passed = true; + bool early_stop = false; + score_t scr; + int ust = 0, usm = 0; + string info = "Accepted"; + score_t unrescaled_min_score = 100; + vector points; + + SubtaskInfo() = default; + SubtaskInfo(const SubtaskMetaInfo &meta) : meta(meta) { + scr = meta.full_score; + } + + inline void update_stats(int _ust, int _usm) { + if (_ust >= 0) { + if (meta.subtask_used_time_type == "max") { + ust = max(ust, _ust); + } else { + ust += _ust; + } + } + if (_usm >= 0) { + usm = max(usm, _usm); + } + } + + inline bool resolve_dependencies(const map &subtasks) { + for (const auto &p : meta.subtask_dependencies) { + const auto &dep = subtasks.at(p); + if (meta.subtask_type == "packed") { + if (!dep.passed) { + passed = false; + scr = 0; + } + } else if (meta.subtask_type == "min") { + unrescaled_min_score = min(unrescaled_min_score, dep.unrescaled_min_score); + scr = scale_score(unrescaled_min_score, meta.full_score); + if (!dep.passed) { + passed = false; + info = "Acceptable Answer"; + } + } + if (scr == 0) { + return false; + } + } + return true; + } + + inline void add_point_info(PointInfo po) { + unrescaled_min_score = min(unrescaled_min_score, po.scr); + update_stats(po.ust, po.usm); + + if (meta.subtask_type == "packed") { + if (po.scr != 100) { + passed = false; + early_stop = true; + po.scr = points.empty() ? 0 : -meta.full_score; + scr = 0; + info = po.info; + } else { + po.scr = points.empty() ? meta.full_score : 0; + scr = meta.full_score; + } + } else if (meta.subtask_type == "min") { + if (po.scr != 100) { + passed = false; + } + po.scr = scale_score(po.scr, meta.full_score); + if (po.scr <= scr) { + scr = po.scr; + info = po.info; + if (meta.full_score != 0 && scr == 0) { + early_stop = true; + } + } + } + points.push_back(po); + } + + friend inline ostream &operator<<(ostream &out, const SubtaskInfo &st_info) { + out << "" << endl; + for (const auto &info : st_info.points) { + out << info; + } + out << "" << endl; + return out; + } }; + struct RunCheckerResult { - int type; - int ust, usm; - int scr; - string info; - - static RunCheckerResult from_file(const string &file_name, const RunResult &rres) { - RunCheckerResult res; - res.type = rres.type; - res.ust = rres.ust; - res.usm = rres.usm; - - if (rres.type != RS_AC) { - res.scr = 0; - } else { - FILE *fres = fopen(file_name.c_str(), "r"); - char type[21]; - if (fres == NULL || fscanf(fres, "%20s", type) != 1) { - return RunCheckerResult::failed_result(); - } - if (strcmp(type, "ok") == 0) { - res.scr = 100; - } else if (strcmp(type, "points") == 0) { - double d; - if (fscanf(fres, "%lf", &d) != 1) { - return RunCheckerResult::failed_result(); - } else { - res.scr = (int)floor(100 * d + 0.5); - } - } else { - res.scr = 0; - } - fclose(fres); - } - res.info = file_preview(file_name); - return res; - } - - static RunCheckerResult failed_result() { - RunCheckerResult res; - res.type = RS_JGF; - res.ust = -1; - res.usm = -1; - res.scr = 0; - res.info = "Checker Judgement Failed"; - return res; - } + int type; + int ust, usm; + score_t scr; + string info; + + static RunCheckerResult from_file(const string &file_name, const RunResult &rres) { + RunCheckerResult res; + res.type = rres.type; + res.ust = rres.ust; + res.usm = rres.usm; + + if (rres.type != runp::RS_AC) { + res.scr = 0; + } else { + FILE *fres = fopen(file_name.c_str(), "r"); + char type[21]; + if (fres == NULL || fscanf(fres, "%20s", type) != 1) { + return RunCheckerResult::failed_result(); + } + if (strcmp(type, "ok") == 0) { + res.scr = 100; + } else if (strcmp(type, "points") == 0) { + double d; + if (fscanf(fres, "%lf", &d) != 1) { + return RunCheckerResult::failed_result(); + } else { + res.scr = 100.0 * d; + } + } else { + res.scr = 0; + } + fclose(fres); + } + res.info = file_preview(file_name); + return res; + } + + static RunCheckerResult failed_result() { + RunCheckerResult res; + res.type = runp::RS_JGF; + res.ust = -1; + res.usm = -1; + res.scr = 0; + res.info = "Checker Judgment Failed"; + return res; + } }; struct RunValidatorResult { - int type; - int ust, usm; - bool succeeded; - string info; - - static RunValidatorResult failed_result() { - RunValidatorResult res; - res.type = RS_JGF; - res.ust = -1; - res.usm = -1; - res.succeeded = 0; - res.info = "Validator Judgement Failed"; - return res; - } + int type; + int ust, usm; + bool succeeded; + string info; + + static RunValidatorResult failed_result() { + RunValidatorResult res; + res.type = runp::RS_JGF; + res.ust = -1; + res.usm = -1; + res.succeeded = 0; + res.info = "Validator Judgment Failed"; + return res; + } }; -struct RunCompilerResult { - int type; - int ust, usm; - bool succeeded; - string info; - - static RunCompilerResult failed_result() { - RunCompilerResult res; - res.type = RS_JGF; - res.ust = -1; - res.usm = -1; - res.succeeded = false; - res.info = "Compile Failed"; - return res; - } +struct run_compiler_result { + runp::RS_TYPE type; + bool succeeded; + string info; + + static run_compiler_result failed_result() { + run_compiler_result res; + res.type = runp::RS_JGF; + res.succeeded = false; + res.info = "Compile Failed"; + return res; + } }; +typedef run_compiler_result RunCompilerResult; // see also: run_simple_interaction struct RunSimpleInteractionResult { - RunResult res; // prog - RunCheckerResult ires; // interactor + RunResult res; // prog + RunCheckerResult ires; // interactor }; int problem_id; @@ -290,11 +714,10 @@ string work_path; string data_path; string result_path; -int tot_time = 0; +int tot_time = 0; int max_memory = 0; -int tot_score = 0; +score_t tot_score = 0; ostringstream details_out; -//vector points_info; map config; /*==================== parameter End ================== */ @@ -302,248 +725,278 @@ map config; /*====================== config set =================== */ void print_config() { - for (map::iterator it = config.begin(); it != config.end(); ++it) { - cerr << it->first << " = " << it->second << endl; - } + for (map::iterator it = config.begin(); it != config.end(); ++it) { + cerr << it->first << " = " << it->second << endl; + } } void load_config(const string &filename) { - ifstream fin(filename.c_str()); - if (!fin) { - return; - } - string key; - string val; - while (fin >> key >> val) { - config[key] = val; - } + ifstream fin(filename.c_str()); + + if (!fin) { + return; + } + + string key, val; + + // the first token of a line is key, the rest is value + while (fin >> key) { + getline(fin, val); + config[key] = trim_copy(val); + } + + // while (fin >> key >> val) { + // config[key] = val; + // } } string conf_str(const string &key, int num, const string &val) { - ostringstream sout; - sout << key << "_" << num; - if (config.count(sout.str()) == 0) { - return val; - } - return config[sout.str()]; + ostringstream sout; + sout << key << "_" << num; + if (config.count(sout.str()) == 0) { + return val; + } + return config[sout.str()]; } string conf_str(const string &key, const string &val) { - if (config.count(key) == 0) { - return val; - } - return config[key]; + if (config.count(key) == 0) { + return val; + } + return config[key]; } string conf_str(const string &key) { - return conf_str(key, ""); + return conf_str(key, ""); } int conf_int(const string &key, const int &val) { - if (config.count(key) == 0) { - return val; - } - return atoi(config[key].c_str()); + if (config.count(key) == 0) { + return val; + } + return atoi(config[key].c_str()); } int conf_int(const string &key, int num, const int &val) { - ostringstream sout; - sout << key << "_" << num; - if (config.count(sout.str()) == 0) { - return conf_int(key, val); - } - return atoi(config[sout.str()].c_str()); -} -int conf_int(const string &key) { - return conf_int(key, 0); + ostringstream sout; + sout << key << "_" << num; + if (config.count(sout.str()) == 0) { + return conf_int(key, val); + } + return atoi(config[sout.str()].c_str()); +} +int conf_int(const string &key) { + return conf_int(key, 0); +} +double conf_double(const string &key, const double &val) { + if (config.count(key) == 0) { + return val; + } + return stod(config[key]); +} +double conf_double(const string &key, int num, const double &val) { + ostringstream sout; + sout << key << "_" << num; + if (config.count(sout.str()) == 0) { + return conf_double(key, val); + } + return stod(config[sout.str()]); +} +double conf_double(const string &key) { + return conf_double(key, 0); +} +score_t conf_score(const string &key, const score_t &val) { + return score_t(conf_double(key, double(val))).rounded_score(); +} +score_t conf_score(const string &key, int num, const score_t &val) { + return score_t(conf_double(key, num, double(val))).rounded_score(); +} +string conf_file_name_with_num(const string &s, int num) { + ostringstream name; + if (num < 0) { + name << "ex_"; + } + name << conf_str(s + "_pre", s) << std::abs(num) << "." << conf_str(s + "_suf", "txt"); + return name.str(); } string conf_input_file_name(int num) { - ostringstream name; - if (num < 0) { - name << "ex_"; - } - name << conf_str("input_pre", "input") << abs(num) << "." << conf_str("input_suf", "txt"); - return name.str(); + return conf_file_name_with_num("input", num); } string conf_output_file_name(int num) { - ostringstream name; - if (num < 0) { - name << "ex_"; - } - name << conf_str("output_pre", "output") << abs(num) << "." << conf_str("output_suf", "txt"); - return name.str(); -} -RunLimit conf_run_limit(string pre, const int &num, const RunLimit &val) { - if (!pre.empty()) { - pre += "_"; - } - RunLimit limit; - limit.time = conf_int(pre + "time_limit", num, val.time); - limit.memory = conf_int(pre + "memory_limit", num, val.memory); - limit.output = conf_int(pre + "output_limit", num, val.output); - return limit; -} -RunLimit conf_run_limit(const int &num, const RunLimit &val) { - return conf_run_limit("", num, val); -} -void conf_add(const string &key, const string &val) { - if (config.count(key)) return; - config[key] = val; -} -bool conf_has(const string &key) { - return config.count(key) != 0; -} -bool conf_is(const string &key, const string &val) { - if (config.count(key) == 0) return false; - return config[key] == val; + return conf_file_name_with_num("output", num); +} +runp::limits_t conf_run_limit(string pre, const int &num, const runp::limits_t &val) { + if (!pre.empty()) { + pre += "_"; + } + runp::limits_t limits; + limits.time = conf_double(pre + "time_limit", num, val.time); + limits.memory = conf_int(pre + "memory_limit", num, val.memory); + limits.output = conf_int(pre + "output_limit", num, val.output); + limits.real_time = conf_double(pre + "real_time_limit", num, val.real_time); + limits.stack = conf_int(pre + "stack_limit", num, val.stack); + return limits; +} +runp::limits_t conf_run_limit(const int &num, const RunLimit &val) { + return conf_run_limit("", num, val); +} +SubtaskMetaInfo conf_subtask_meta_info(string pre, const int &num) { + if (!pre.empty()) { + pre += "_"; + } + + int nT = conf_int(pre + "n_subtasks", 0); + + SubtaskMetaInfo meta; + meta.num = num; + + int startI = conf_int(pre + "subtask_end", num - 1, 0) + 1; + int endI = num < nT ? conf_int(pre + "subtask_end", num, 0) : conf_int(pre + "n_tests", 10); + for (int i = startI; i <= endI; i++) { + meta.points_id.push_back(i); + } + + meta.subtask_type = conf_str(pre + "subtask_type", num, "packed"); + meta.subtask_used_time_type = conf_str(pre + "subtask_used_time_type", num, "sum"); + meta.full_score = conf_score(pre + "subtask_score", num, 100.0 / nT); + if (conf_str("subtask_dependence", num, "none") == "many") { + string cur = "subtask_dependence_" + vtos(num); + int p = 1; + while (conf_int(cur, p, 0) != 0) { + meta.subtask_dependencies.push_back(conf_int(cur, p, 0)); + p++; + } + } else if (conf_int("subtask_dependence", num, 0) != 0) { + meta.subtask_dependencies.push_back(conf_int("subtask_dependence", num, 0)); + } + return meta; +} +SubtaskMetaInfo conf_subtask_meta_info(const int &num) { + return conf_subtask_meta_info("", num); +} +void conf_add(const string &key, const string &val) { + if (config.count(key)) return; + config[key] = val; +} +bool conf_has(const string &key) { + return config.count(key) != 0; +} +bool conf_is(const string &key, const string &val) { + if (config.count(key) == 0) return false; + return config[key] == val; } /*==================== config set End ================= */ /*====================== info print =================== */ -template -inline string vtos(const T &v) { - ostringstream sout; - sout << v; - return sout.str(); +inline string info_str(int id) { + return runp::rstype_str((runp::RS_TYPE)id); } - -inline string htmlspecialchars(const string &s) { - string r; - for (int i = 0; i < (int)s.length(); i++) { - switch (s[i]) { - case '&' : r += "&"; break; - case '<' : r += "<"; break; - case '>' : r += ">"; break; - case '"' : r += """; break; - case '\0': r += "\\0"; break; - default : r += s[i]; break; - } - } - return r; -} - -inline string info_str(int id) { - switch (id) { - case RS_MLE: return "Memory Limit Exceeded"; - case RS_TLE: return "Time Limit Exceeded"; - case RS_OLE: return "Output Limit Exceeded"; - case RS_RE : return "Runtime Error"; - case RS_DGS: return "Dangerous Syscalls"; - case RS_JGF: return "Judgement Failed"; - default : return "Unknown Result"; - } -} -inline string info_str(const RunResult &p) { - return info_str(p.type); +inline string info_str(const RunResult &p) { + return info_str(p.type); } void add_point_info(const PointInfo &info, bool update_tot_score = true) { - if (info.num >= 0) { - if(info.ust >= 0) { - tot_time += info.ust; - } - if(info.usm >= 0) { - max_memory = max(max_memory, info.usm); - } - } - if (update_tot_score) { - tot_score += info.scr; - } - - details_out << "" << endl; - if (conf_str("show_in", "on") == "on") { - details_out << "" << htmlspecialchars(info.in) << "" << endl; - } - if (conf_str("show_out", "on") == "on") { - details_out << "" << htmlspecialchars(info.out) << "" << endl; - } - if (conf_str("show_res", "on") == "on") { - details_out << "" << htmlspecialchars(info.res) << "" << endl; - } - details_out << "" << endl; + if (info.num >= 0) { + if (info.ust >= 0) { + tot_time += info.ust; + } + if (info.usm >= 0) { + max_memory = max(max_memory, info.usm); + } + } + if (update_tot_score) { + tot_score = (tot_score + info.scr).rounded_score(); + } + + details_out << info; } void add_custom_test_info(const CustomTestInfo &info) { - if(info.ust >= 0) { - tot_time += info.ust; - } - if(info.usm >= 0) { - max_memory = max(max_memory, info.usm); - } - - details_out << "" << endl; - if (!info.exp.empty()) { - details_out << info.exp << endl; - } - details_out << "" << htmlspecialchars(info.out) << "" << endl; - details_out << "" << endl; -} -void add_subtask_info(const int &num, const int &scr, const string &info, const vector &points) { - details_out << "" << endl; - tot_score += scr; - for (vector::const_iterator it = points.begin(); it != points.end(); it++) { - add_point_info(*it, false); - } - details_out << "" << endl; + if (info.ust >= 0) { + tot_time += info.ust; + } + if (info.usm >= 0) { + max_memory = max(max_memory, info.usm); + } + + details_out << "" << endl; + if (!info.exp.empty()) { + details_out << info.exp << endl; + } + details_out << "" << htmlspecialchars(info.out) << "" << endl; + details_out << "" << endl; +} +void add_subtask_info(const SubtaskInfo &st_info) { + if (st_info.ust >= 0) { + tot_time += st_info.ust; + } + if (st_info.usm >= 0) { + max_memory = max(max_memory, st_info.usm); + } + tot_score = (tot_score + st_info.scr).rounded_score(); + details_out << st_info; } void end_judge_ok() { - FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); - fprintf(fres, "score %d\n", tot_score); - fprintf(fres, "time %d\n", tot_time); - fprintf(fres, "memory %d\n", max_memory); - fprintf(fres, "details\n"); - fprintf(fres, "\n"); - fprintf(fres, "%s", details_out.str().c_str()); - fprintf(fres, "\n"); - fclose(fres); - exit(0); + ofstream fres(result_path + "/result.txt"); + if (!fres) { + exit(1); + } + fres << "score " << tot_score << "\n"; + fres << "time " << tot_time << "\n"; + fres << "memory " << max_memory << "\n"; + fres << "details\n"; + fres << "\n"; + fres << details_out.str(); + fres << "\n"; + fres.close(); + exit(0); } void end_judge_judgement_failed(const string &info) { - FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); - fprintf(fres, "error Judgement Failed\n"); - fprintf(fres, "details\n"); - fprintf(fres, "%s\n", htmlspecialchars(info).c_str()); - fclose(fres); - exit(1); + ofstream fres(result_path + "/result.txt"); + if (!fres) { + exit(1); + } + fres << "error Judgment Failed\n"; + fres << "details\n"; + fres << "" << htmlspecialchars(info) << "\n"; + fres.close(); + exit(0); } void end_judge_compile_error(const RunCompilerResult &res) { - FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); - fprintf(fres, "error Compile Error\n"); - fprintf(fres, "details\n"); - fprintf(fres, "%s\n", htmlspecialchars(res.info).c_str()); - fclose(fres); - exit(0); + ofstream fres(result_path + "/result.txt"); + if (!fres) { + exit(1); + } + fres << "error Compile Error\n"; + fres << "details\n"; + fres << "" << htmlspecialchars(res.info) << "\n"; + fres.close(); + exit(0); } void report_judge_status(const char *status) { - FILE *f = fopen((result_path + "/cur_status.txt").c_str(), "a"); - if (f == NULL) { - return; - } - if (flock(fileno(f), LOCK_EX) != -1) { - if (ftruncate(fileno(f), 0) != -1) { - fprintf(f, "%s\n", status); - fflush(f); - } - flock(fileno(f), LOCK_UN); - } - fclose(f); + FILE *f = fopen((result_path + "/cur_status.txt").c_str(), "a"); + if (f == NULL) { + return; + } + if (flock(fileno(f), LOCK_EX) != -1) { + if (ftruncate(fileno(f), 0) != -1) { + fprintf(f, "%s\n", status); + fflush(f); + } + flock(fileno(f), LOCK_UN); + } + fclose(f); } bool report_judge_status_f(const char *fmt, ...) { - const int MaxL = 512; - char status[MaxL]; - va_list ap; - va_start(ap, fmt); - int res = vsnprintf(status, MaxL, fmt, ap); - if (res < 0 || res >= MaxL) { - return false; - } - report_judge_status(status); - va_end(ap); - return true; + const int MaxL = 512; + char status[MaxL]; + va_list ap; + va_start(ap, fmt); + int res = vsnprintf(status, MaxL, fmt, ap); + if (res < 0 || res >= MaxL) { + return false; + } + report_judge_status(status); + va_end(ap); + return true; } /*==================== info print End ================= */ @@ -551,748 +1004,372 @@ bool report_judge_status_f(const char *fmt, ...) { /*========================== run ====================== */ struct RunProgramConfig { - vector readable_file_names; // other than stdin - string result_file_name; - string input_file_name; - string output_file_name; - string error_file_name; - string type; - string work_path; - RunLimit limit; - vector argv; - - RunProgramConfig() { - int p = 1; - while (conf_str("readable", p, "") != "") { - readable_file_names.push_back(conf_str("readable", p, "")); - p++; - } - result_file_name = result_path + "/run_program_result.txt"; - type = "default"; - work_path = ::work_path; - - for (vector::iterator it = readable_file_names.begin(); it != readable_file_names.end(); it++) { - if (!it->empty() && (*it)[0] != '/') { - *it = ::work_path + "/" + *it; - } - } - } - - void set_argv(const char *program_name, ...) { - argv.clear(); - argv.push_back(program_name); - va_list vl; - va_start(vl, program_name); - for (const char *arg = va_arg(vl, const char *); arg; arg = va_arg(vl, const char *)) { - argv.push_back(arg); - } - va_end(vl); - } - - void set_submission_program_name(string name) { - string lang = conf_str(string(name) + "_language"); - type = "default"; - string program_name = name; - if (lang == "Python2") { - type = "python2"; - } else if (lang == "Python3") { - type = "python3"; - } else if (lang == "Java8") { - program_name += "." + conf_str(name + "_main_class"); - type = "java8"; - } else if (lang == "Java11") { - program_name += "." + conf_str(name + "_main_class"); - type = "java11"; - } - - set_argv(program_name.c_str(), NULL); - } - - string get_cmd() const { - ostringstream sout; - sout << main_path << "/run/run_program" - << " " << "--res=" << escapeshellarg(result_file_name) - << " " << "--in=" << escapeshellarg(input_file_name) - << " " <<"--out=" << escapeshellarg(output_file_name) - << " " << "--err=" << escapeshellarg(error_file_name) - << " " << "--tl=" << limit.time - << " " << "--ml=" << limit.memory - << " " << "--ol=" << limit.output - << " " << "--type=" << type - << " " << "--work-path=" << work_path - /*<< " " << "--show-trace-details"*/; - for (vector::const_iterator it = readable_file_names.begin(); it != readable_file_names.end(); it++) { - sout << " " << "--add-readable=" << escapeshellarg(*it); - } - for (vector::const_iterator it = argv.begin(); it != argv.end(); it++) { - sout << " " << escapeshellarg(*it); - } - return sout.str(); - } + vector readable_file_names; // other than stdin + vector writable_file_names; // other than stdout, stderr + string result_file_name; + string input_file_name; + string output_file_name; + string error_file_name; + string type; + string work_path; + RunLimit limit; + vector argv; + + RunProgramConfig() { + int p = 1; + while (conf_str("readable", p, "") != "") { + readable_file_names.push_back(conf_str("readable", p, "")); + p++; + } + result_file_name = result_path + "/run_program_result.txt"; + type = "default"; + work_path = ::work_path; + + for (vector::iterator it = readable_file_names.begin(); + it != readable_file_names.end(); it++) { + if (!it->empty() && (*it)[0] != '/') { + *it = ::work_path + "/" + *it; + } + } + } + + void set_argv(const char *program_name, ...) { + argv.clear(); + argv.push_back(program_name); + va_list vl; + va_start(vl, program_name); + for (const char *arg = va_arg(vl, const char *); arg; arg = va_arg(vl, const char *)) { + argv.push_back(arg); + } + va_end(vl); + } + + void set_submission_program_name(const string &name) { + string lang = conf_str(name + "_language"); + if (lang.empty()) { + type = conf_str(name + "_run_type", "default"); + } else { + type = runp::get_type_from_lang(lang); + } + set_argv(name.c_str(), NULL); + } + + string get_cmd() const { + ostringstream sout; + sout << main_path << "/run/run_program" + << " " << "--res=" << escapeshellarg(result_file_name) << " " + << "--in=" << escapeshellarg(input_file_name) << " " + << "--out=" << escapeshellarg(output_file_name) << " " + << "--err=" << escapeshellarg(error_file_name) << " " << "--tl=" << limit.time << " " + << "--ml=" << limit.memory << " " << "--ol=" << limit.output << " " + << "--type=" << type << " " << "--work-path=" << work_path + /*<< " " << "--show-trace-details"*/; + + if (limit.real_time != -1) { + sout << " " << "--rtl=" << limit.real_time; + } + if (limit.stack != -1) { + sout << " " << "--sl=" << limit.stack; + } + + for (vector::const_iterator it = readable_file_names.begin(); + it != readable_file_names.end(); it++) { + sout << " " << "--add-readable=" << escapeshellarg(*it); + } + for (vector::const_iterator it = writable_file_names.begin(); + it != writable_file_names.end(); it++) { + sout << " " << "--add-writable=" << escapeshellarg(*it); + } + for (vector::const_iterator it = argv.begin(); it != argv.end(); it++) { + sout << " " << escapeshellarg(*it); + } + + string command = sout.str(); + + return command; + } }; -struct PipeConfig { - int from, to; - int from_fd, to_fd; - - string saving_file_name; - - PipeConfig() { - } - PipeConfig(int _from, int _from_fd, int _to, int _to_fd, const string &_saving_file_name = "") - : from(_from), from_fd(_from_fd), to(_to), to_fd(_to_fd), saving_file_name(_saving_file_name) { - } -}; -struct RunInteractionConfig { - vector cmds; - vector pipes; - - string get_cmd() const { - ostringstream sout; - sout << main_path << "/run/run_interaction"; - for (int i = 0; i < (int)cmds.size(); i++) { - sout << " " << escapeshellarg(cmds[i]); - } - for (int i = 0; i < (int)pipes.size(); i++) { - sout << " " << "-p"; - sout << " " << pipes[i].from << ":" << pipes[i].from_fd; - sout << "-" << pipes[i].to << ":" << pipes[i].to_fd; - - if (!pipes[i].saving_file_name.empty()) { - sout << " " << "-s"; - sout << " " << escapeshellarg(pipes[i].saving_file_name); - } - } - return sout.str(); - } -}; +typedef runp::interaction::pipe_config PipeConfig; +typedef runp::interaction::config RunInteractionConfig; // @deprecated // will be removed in the future -RunResult vrun_program( - const char *run_program_result_file_name, - const char *input_file_name, - const char *output_file_name, - const char *error_file_name, - const RunLimit &limit, - const vector &rest) { - ostringstream sout; - sout << main_path << "/run/run_program" - << " " << "--res=" << escapeshellarg(run_program_result_file_name) - << " " << "--in=" << escapeshellarg(input_file_name) - << " " <<"--out=" << escapeshellarg(output_file_name) - << " " << "--err=" << escapeshellarg(error_file_name) - << " " << "--tl=" << limit.time - << " " << "--ml=" << limit.memory - << " " << "--ol=" << limit.output - /*<< " " << "--show-trace-details"*/; - for (vector::const_iterator it = rest.begin(); it != rest.end(); it++) { - sout << " " << escapeshellarg(*it); - } - - if (execute(sout.str().c_str()) != 0) { - return RunResult::failed_result(); - } - return RunResult::from_file(run_program_result_file_name); +RunResult vrun_program(const char *run_program_result_file_name, const char *input_file_name, + const char *output_file_name, const char *error_file_name, + const RunLimit &limit, const vector &rest) { + ostringstream sout; + sout << main_path << "/run/run_program" + << " " << "--res=" << escapeshellarg(run_program_result_file_name) << " " + << "--in=" << escapeshellarg(input_file_name) << " " + << "--out=" << escapeshellarg(output_file_name) << " " + << "--err=" << escapeshellarg(error_file_name) << " " << "--tl=" << limit.time << " " + << "--ml=" << limit.memory << " " << "--ol=" << limit.output + /*<< " " << "--show-trace-details"*/; + for (vector::const_iterator it = rest.begin(); it != rest.end(); it++) { + sout << " " << escapeshellarg(*it); + } + + string command = sout.str(); + + if (execute(command) != 0) { + return RunResult::failed_result(); + } + return RunResult::from_file(run_program_result_file_name); } RunResult run_program(const RunProgramConfig &rpc) { - if (execute(rpc.get_cmd().c_str()) != 0) { - return RunResult::failed_result(); - } - return RunResult::from_file(rpc.result_file_name); + if (execute(rpc.get_cmd()) != 0) { + return RunResult::failed_result(); + } + return RunResult::from_file(rpc.result_file_name); +} + +RunResult run_program(const runp::config &rpc) { + if (execute(rpc.get_cmd()) != 0) { + return RunResult::failed_result(); + } + return RunResult::from_file(rpc.result_file_name); } // @return interaction return value int run_interaction(const RunInteractionConfig &ric) { - return execute(ric.get_cmd().c_str()); -} - -RunResult run_program( - const char *run_program_result_file_name, - const char *input_file_name, - const char *output_file_name, - const char *error_file_name, - const RunLimit &limit, ...) { - vector argv; - va_list vl; - va_start(vl, limit); - for (const char *arg = va_arg(vl, const char *); arg; arg = va_arg(vl, const char *)) { - argv.push_back(arg); - } - va_end(vl); - return vrun_program(run_program_result_file_name, - input_file_name, - output_file_name, - error_file_name, - limit, - argv); -} - -RunValidatorResult run_validator( - const string &input_file_name, - const RunLimit &limit, - const string &program_name) { - RunResult ret = run_program( - (string(result_path) + "/run_validator_result.txt").c_str(), - input_file_name.c_str(), - "/dev/null", - (string(result_path) + "/validator_error.txt").c_str(), - limit, - program_name.c_str(), - NULL); - - RunValidatorResult res; - res.type = ret.type; - res.ust = ret.ust; - res.usm = ret.usm; - - if (ret.type != RS_AC || ret.exit_code != 0) { - res.succeeded = false; - res.info = file_preview(result_path + "/validator_error.txt"); - } else { - res.succeeded = true; - } - return res; -} -RunCheckerResult run_checker( - const RunLimit &limit, - const string &program_name, - const string &input_file_name, - const string &output_file_name, - const string &answer_file_name) { - RunResult ret = run_program( - (string(result_path) + "/run_checker_result.txt").c_str(), - "/dev/null", - "/dev/null", - (string(result_path) + "/checker_error.txt").c_str(), - limit, - ("--add-readable=" + input_file_name).c_str(), - ("--add-readable=" + output_file_name).c_str(), - ("--add-readable=" + answer_file_name).c_str(), - program_name.c_str(), - realpath(input_file_name).c_str(), - realpath(output_file_name).c_str(), - realpath(answer_file_name).c_str(), - NULL); - - return RunCheckerResult::from_file(result_path + "/checker_error.txt", ret); -} - -RunCompilerResult run_compiler(const char *path, ...) { - vector argv; - argv.push_back("--type=compiler"); - argv.push_back(string("--work-path=") + path); - va_list vl; - va_start(vl, path); - for (const char *arg = va_arg(vl, const char *); arg; arg = va_arg(vl, const char *)) { - argv.push_back(arg); - } - va_end(vl); - - RunResult ret = vrun_program( - (result_path + "/run_compiler_result.txt").c_str(), - "/dev/null", - "stderr", - (result_path + "/compiler_result.txt").c_str(), - RL_COMPILER_DEFAULT, - argv); - RunCompilerResult res; - res.type = ret.type; - res.ust = ret.ust; - res.usm = ret.usm; - res.succeeded = ret.type == RS_AC && ret.exit_code == 0; - if (!res.succeeded) { - if (ret.type == RS_AC) { - res.info = file_preview(result_path + "/compiler_result.txt", 500); - } else if (ret.type == RS_JGF) { - res.info = "No Comment"; - } else { - res.info = "Compiler " + info_str(ret.type); - } - } - return res; -} - -RunResult run_submission_program( - const string &input_file_name, - const string &output_file_name, - const RunLimit &limit, - const string &name, - RunProgramConfig rpc = RunProgramConfig()) { - rpc.result_file_name = result_path + "/run_submission_program.txt"; - rpc.input_file_name = input_file_name; - rpc.output_file_name = output_file_name; - rpc.error_file_name = "/dev/null"; - rpc.limit = limit; - rpc.set_submission_program_name(name); - - RunResult res = run_program(rpc); - if (res.type == RS_AC && res.exit_code != 0) { - res.type = RS_RE; - } - return res; -} - -void prepare_interactor() { - static bool prepared = false; - if (prepared) { - return; - } - string data_path_std = data_path + "/interactor"; - string work_path_std = work_path + "/interactor"; - executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str()); - conf_add("interactor_language", "C++"); - prepared = true; -} - -// simple: prog <---> interactor <---> data -RunSimpleInteractionResult run_simple_interation( - const string &input_file_name, - const string &answer_file_name, - const string &real_input_file_name, - const string &real_output_file_name, - const RunLimit &limit, - const RunLimit &ilimit, - const string &name, - RunProgramConfig rpc = RunProgramConfig(), - RunProgramConfig irpc = RunProgramConfig()) { - prepare_interactor(); - - rpc.result_file_name = result_path + "/run_submission_program.txt"; - rpc.input_file_name = "stdin"; - rpc.output_file_name = "stdout"; - rpc.error_file_name = "/dev/null"; - rpc.limit = limit; - rpc.set_submission_program_name(name); - - irpc.result_file_name = result_path + "/run_interactor_program.txt"; - irpc.readable_file_names.push_back(input_file_name); - irpc.readable_file_names.push_back(answer_file_name); - irpc.input_file_name = "stdin"; - irpc.output_file_name = "stdout"; - irpc.error_file_name = result_path + "/interactor_error.txt"; - irpc.limit = ilimit; - irpc.set_submission_program_name("interactor"); - irpc.argv.push_back(input_file_name); - irpc.argv.push_back("/dev/stdin"); - irpc.argv.push_back(answer_file_name); - - irpc.limit.real_time = rpc.limit.real_time = rpc.limit.time + irpc.limit.time + 1; - - RunInteractionConfig ric; - ric.cmds.push_back(rpc.get_cmd()); - ric.cmds.push_back(irpc.get_cmd()); - - // from:fd, to:fd - ric.pipes.push_back(PipeConfig(2, 1, 1, 0, real_input_file_name)); - ric.pipes.push_back(PipeConfig(1, 1, 2, 0, real_output_file_name)); - - run_interaction(ric); - - RunSimpleInteractionResult rires; - RunResult res = RunResult::from_file(rpc.result_file_name); - RunCheckerResult ires = RunCheckerResult::from_file(irpc.error_file_name, RunResult::from_file(irpc.result_file_name)); - - if (res.type == RS_AC && res.exit_code != 0) { - res.type = RS_RE; - } - - if (ires.type == RS_JGF) { - ires.info = "Interactor Judgement Failed"; - } - if (ires.type == RS_TLE) { - ires.type = RS_AC; - res.type = RS_TLE; - } - - rires.res = res; - rires.ires = ires; - return rires; -} - -void prepare_run_standard_program() { - static bool prepared = false; - if (prepared) { - return; - } - string data_path_std = data_path + "/std"; - string work_path_std = work_path + "/std"; - executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str()); - conf_add("std_language", "C++"); - prepared = true; + return runp::interaction::run(ric); +} + +RunResult run_program(const char *run_program_result_file_name, const char *input_file_name, + const char *output_file_name, const char *error_file_name, + const RunLimit &limit, ...) { + vector argv; + va_list vl; + va_start(vl, limit); + for (const char *arg = va_arg(vl, const char *); arg; arg = va_arg(vl, const char *)) { + argv.push_back(arg); + } + va_end(vl); + return vrun_program(run_program_result_file_name, input_file_name, output_file_name, + error_file_name, limit, argv); +} + +RunValidatorResult run_validator(const string &input_file_name, const RunLimit &limit, + const string &program_name) { + RunResult ret = run_program( + (string(result_path) + "/run_validator_result.txt").c_str(), input_file_name.c_str(), + "/dev/null", (string(result_path) + "/validator_error.txt").c_str(), limit, + ("--type=" + conf_str("val_run_type", "default")).c_str(), program_name.c_str(), NULL); + + RunValidatorResult res; + res.type = ret.type; + res.ust = ret.ust; + res.usm = ret.usm; + + if (ret.type != runp::RS_AC || ret.exit_code != 0) { + res.succeeded = false; + res.info = file_preview(result_path + "/validator_error.txt"); + } else { + res.succeeded = true; + } + return res; +} +RunCheckerResult run_checker(const RunLimit &limit, const string &program_name, + const string &input_file_name, const string &output_file_name, + const string &answer_file_name) { + RunResult ret = + run_program((result_path + "/run_checker_result.txt").c_str(), "/dev/null", "/dev/null", + (result_path + "/checker_error.txt").c_str(), limit, + ("--add-readable=" + input_file_name).c_str(), + ("--add-readable=" + output_file_name).c_str(), + ("--add-readable=" + answer_file_name).c_str(), + ("--type=" + conf_str("chk_run_type", "default")).c_str(), program_name.c_str(), + realpath(input_file_name).c_str(), realpath(output_file_name).c_str(), + realpath(answer_file_name).c_str(), NULL); + + return RunCheckerResult::from_file(result_path + "/checker_error.txt", ret); +} + +template +run_compiler_result run_compiler(runp::config rpc) { + rpc.result_file_name = result_path + "/run_compiler_result.txt"; + rpc.input_file_name = "/dev/null"; + rpc.output_file_name = "stderr"; + rpc.error_file_name = result_path + "/compiler_result.txt"; + rpc.type = "compiler"; + + runp::result ret = run_program(rpc); + run_compiler_result res; + res.type = ret.type; + res.succeeded = ret.type == runp::RS_AC && ret.exit_code == 0; + if (!res.succeeded) { + if (ret.type == runp::RS_AC) { + res.info = file_preview(result_path + "/compiler_result.txt", 10240); + } else if (ret.type == runp::RS_JGF) { + res.info = "No Comment"; + } else { + res.info = "Compiler " + runp::rstype_str(ret.type); + } + } + return res; +} + +RunResult run_submission_program(const string &input_file_name, const string &output_file_name, + const RunLimit &limit, const string &name, + RunProgramConfig rpc = RunProgramConfig()) { + rpc.result_file_name = result_path + "/run_submission_program.txt"; + rpc.input_file_name = input_file_name; + rpc.output_file_name = output_file_name; + rpc.error_file_name = "/dev/null"; + rpc.limit = limit; + rpc.set_submission_program_name(name); + + RunResult res = run_program(rpc); + if (res.type == runp::RS_AC && res.exit_code != 0) { + res.type = runp::RS_RE; + } + return res; +} + +/** + * prepare_run_standard_program(): do some preparation operations for the std. + * For the current version, it only moves the std to the work dir if it has not been done yet. + * + * @param prepared if it is not -1, set the internal variable as if the preparation operations have + * been done or not (depending on prepared is non-zero or not) + */ +void prepare_run_standard_program(int prepared = -1) { + static int _prepared = 0; + if (prepared != -1) { + _prepared = prepared; + } + if (_prepared) { + return; + } + string data_path_std = data_path + "/std"; + string work_path_std = work_path + "/std"; + executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str()); + _prepared = 1; +} + +/** + * prepare_interactor(): do some preparation operations for the interactor. + * For the current version, it only moves the interactor to the work dir if it has not been done + * yet. + * + * @param prepared if it is not -1, set the internal variable as if the preparation operations have + * been done or not (depending on prepared is non-zero or not) + */ +void prepare_interactor(int prepared = -1) { + static int _prepared = 0; + if (prepared != -1) { + _prepared = prepared; + } + if (_prepared) { + return; + } + string data_path_std = data_path + "/interactor"; + string work_path_std = work_path + "/interactor"; + executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str()); + _prepared = 1; +} + +/** + * @brief simple: prog <---> interactor <---> data + */ +RunSimpleInteractionResult run_simple_interaction( + const string &input_file_name, const string &answer_file_name, + const string &real_input_file_name, const string &real_output_file_name, const RunLimit &limit, + const RunLimit &ilimit, const string &name, RunProgramConfig rpc = RunProgramConfig(), + RunProgramConfig irpc = RunProgramConfig()) { + prepare_interactor(); + + rpc.result_file_name = result_path + "/run_submission_program.txt"; + rpc.input_file_name = "stdin"; + rpc.output_file_name = "stdout"; + rpc.error_file_name = "/dev/null"; + rpc.limit = limit; + rpc.set_submission_program_name(name); + + irpc.result_file_name = result_path + "/run_interactor_program.txt"; + irpc.readable_file_names.push_back(input_file_name); + irpc.readable_file_names.push_back(answer_file_name); + irpc.input_file_name = "stdin"; + irpc.output_file_name = "stdout"; + irpc.error_file_name = result_path + "/interactor_error.txt"; + irpc.limit = ilimit; + irpc.set_argv("interactor", input_file_name.c_str(), "/dev/stdin", answer_file_name.c_str(), + NULL); + irpc.type = conf_str("interactor_run_type", "default"); + + rpc.limit.real_time = rpc.limit.time + irpc.limit.time + 1; + irpc.limit.real_time = + rpc.limit.real_time + 1; // one more second to prevent interactor from TLE + + RunInteractionConfig ric; + ric.cmds.push_back(rpc.get_cmd()); + ric.cmds.push_back(irpc.get_cmd()); + + // from:fd, to:fd + ric.pipes.push_back(PipeConfig(2, 1, 1, 0, real_input_file_name)); + ric.pipes.push_back(PipeConfig(1, 1, 2, 0, real_output_file_name)); + + run_interaction(ric); + + RunSimpleInteractionResult rires; + RunResult res = RunResult::from_file(rpc.result_file_name); + RunCheckerResult ires = RunCheckerResult::from_file( + irpc.error_file_name, RunResult::from_file(irpc.result_file_name)); + + if (res.type == runp::RS_AC && res.exit_code != 0) { + res.type = runp::RS_RE; + } + + if (ires.type == runp::RS_JGF) { + ires.info = "Interactor Judgment Failed"; + } + if (ires.type == runp::RS_TLE) { + ires.type = runp::RS_AC; + res.type = runp::RS_TLE; + } + + rires.res = res; + rires.ires = ires; + return rires; } // @deprecated // will be removed in the future -RunResult run_standard_program( - const string &input_file_name, - const string &output_file_name, - const RunLimit &limit, - RunProgramConfig rpc = RunProgramConfig()) { - prepare_run_standard_program(); - rpc.result_file_name = result_path + "/run_standard_program.txt"; - return run_submission_program( - input_file_name, - output_file_name, - limit, - "std", - rpc); +RunResult run_standard_program(const string &input_file_name, const string &output_file_name, + const RunLimit &limit, RunProgramConfig rpc = RunProgramConfig()) { + prepare_run_standard_program(); + rpc.result_file_name = result_path + "/run_standard_program.txt"; + return run_submission_program(input_file_name, output_file_name, limit, "std", rpc); } /*======================== run End ==================== */ /*======================== compile ==================== */ -bool is_illegal_keyword(const string &name) { - if (name == "__asm" || name == "__asm__" || name == "asm") - return true; - return false; -} - -bool has_illegal_keywords_in_file(const string &name) { - FILE *f = fopen(name.c_str(), "r"); - - int c; - string key; - while ((c = fgetc(f)) != EOF) - { - if (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_') - { - if (key.size() < 20) - key += c; - else - { - if (is_illegal_keyword(key)) - return true; - key.erase(key.begin()); - key += c; - } - } - else - { - if (is_illegal_keyword(key)) - return true; - key.clear(); - } - } - if (is_illegal_keyword(key)) - return true; - fclose(f); - return false; -} - -RunCompilerResult prepare_java_source(const string &name, const string &path = work_path) { - FILE *f = fopen((path + "/" + name + ".code").c_str(), "r"); - - const int L = 1024; - - std::string s; - char buf[L + 1]; - - int mode = 0; - - while (!feof(f)) { - buf[fread(buf, 1, L, f)] = '\0'; - - for (char *p = buf; *p; p++) { - s += *p; - switch (mode) { - case 0: - switch (*p) { - case '/': - mode = 1; - break; - case '\'': - mode = 5; - break; - case '\"': - mode = 6; - break; - } - break; - case 1: - switch (*p) { - case '/': - mode = 2; - s.resize(s.length() - 2); - break; - case '*': - mode = 3; - s.resize(s.length() - 2); - break; - } - break; - case 2: - s.resize(s.length() - 1); - switch (*p) { - case '\n': - s += '\n'; - mode = 0; - break; - } - break; - case 3: - s.resize(s.length() - 1); - switch (*p) { - case '*': - mode = 4; - break; - } - break; - case 4: - s.resize(s.length() - 1); - switch (*p) { - case '/': - s += ' '; - mode = 0; - break; - } - break; - case 5: - switch (*p) { - case '\'': - mode = 0; - break; - case '\\': - mode = 7; - break; - } - case 6: - switch (*p) { - case '\"': - mode = 0; - break; - case '\\': - mode = 8; - break; - } - case 7: - mode = 5; - break; - case 8: - mode = 6; - break; - } - } - } - - bool valid[256]; - fill(valid, valid + 256, false); - for (int c = 'a'; c <= 'z'; c++) - valid[c] = true; - for (int c = 'A'; c <= 'Z'; c++) - valid[c] = true; - valid['.'] = true; - valid['_'] = true; - - vector tokens; - for (int p = 0, np = 0; p < (int)s.length(); p = np) { - while (np < (int)s.length() && valid[(unsigned char)s[np]]) { - np++; - } - if (np == p) { - np++; - } else { - tokens.push_back(s.substr(p, np - p)); - } - } - if (tokens.size() > 0 && tokens[0] == "package") { - RunCompilerResult res; - res.type = RS_WA; - res.ust = -1; - res.usm = -1; - res.succeeded = false; - res.info = "Please don't specify the package."; - return res; - } - - for (int i = 0; i + 1 < (int)tokens.size(); i++) { - if (tokens[i] == "class") { - if (tokens[i + 1].length() <= 100) { - config[name + "_main_class"] = tokens[i + 1]; - - RunCompilerResult res; - res.type = RS_AC; - res.ust = 0; - res.usm = 0; - res.succeeded = true; - return res; - } else { - RunCompilerResult res; - res.type = RS_WA; - res.ust = -1; - res.usm = -1; - res.succeeded = false; - res.info = "The name of the main class is too long."; - return res; - } - } - } - - RunCompilerResult res; - res.type = RS_WA; - res.ust = -1; - res.usm = -1; - res.succeeded = false; - res.info = "Can't find the main class."; - return res; -} - -RunCompilerResult compile_c(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/gcc", "-o", name.c_str(), "-x", "c", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL); -} -RunCompilerResult compile_pas(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/fpc", (name + ".code").c_str(), "-O2", NULL); -} -RunCompilerResult compile_cpp(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL); -} -RunCompilerResult compile_cpp11(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", "-std=c++11", NULL); -} -RunCompilerResult compile_python2(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/python2", "-E", "-s", "-B", "-O", "-c", - ("import py_compile\nimport sys\ntry:\n py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n sys.exit(0)\nexcept Exception as e:\n print e\n sys.exit(1)").c_str(), NULL); -} -RunCompilerResult compile_python3(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/python3", "-I", "-B", "-O", "-c", ("import py_compile\nimport sys\ntry:\n py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n sys.exit(0)\nexcept Exception as e:\n print(e)\n sys.exit(1)").c_str(), NULL); -} -RunCompilerResult compile_java8(const string &name, const string &path = work_path) { - RunCompilerResult ret = prepare_java_source(name, path); - if (!ret.succeeded) - return ret; - - string main_class = conf_str(name + "_main_class"); - - executef("rm %s/%s -rf 2>/dev/null; mkdir %s/%s", path.c_str(), name.c_str(), path.c_str(), name.c_str()); - executef("echo package %s\\; | cat - %s/%s.code >%s/%s/%s.java", name.c_str(), path.c_str(), name.c_str(), path.c_str(), name.c_str(), main_class.c_str()); - - return run_compiler((path + "/" + name).c_str(), - "/usr/lib/jvm/java-8-openjdk-amd64/bin/javac", (main_class + ".java").c_str(), NULL); -} -RunCompilerResult compile_java11(const string &name, const string &path = work_path) { - RunCompilerResult ret = prepare_java_source(name, path); - if (!ret.succeeded) - return ret; - - string main_class = conf_str(name + "_main_class"); - - executef("rm %s/%s -rf 2>/dev/null; mkdir %s/%s", path.c_str(), name.c_str(), path.c_str(), name.c_str()); - executef("echo package %s\\; | cat - %s/%s.code >%s/%s/%s.java", name.c_str(), path.c_str(), name.c_str(), path.c_str(), name.c_str(), main_class.c_str()); - - return run_compiler((path + "/" + name).c_str(), - "/usr/lib/jvm/java-11-openjdk-amd64/bin/javac", (main_class + ".java").c_str(), NULL); -} - -RunCompilerResult compile(const char *name) { - string lang = conf_str(string(name) + "_language"); - - if ((lang == "C++" || lang == "C++11" || lang == "C") && has_illegal_keywords_in_file(work_path + "/" + name + ".code")) - { - RunCompilerResult res; - res.type = RS_DGS; - res.ust = -1; - res.usm = -1; - res.succeeded = false; - res.info = "Compile Failed"; - return res; - } - - if (lang == "C++") { - return compile_cpp(name); - } - if (lang == "C++11") { - return compile_cpp11(name); - } - if (lang == "Python2") { - return compile_python2(name); - } - if (lang == "Python3") { - return compile_python3(name); - } - if (lang == "Java8") { - return compile_java8(name); - } - if (lang == "Java11") { - return compile_java11(name); - } - if (lang == "C") { - return compile_c(name); - } - if (lang == "Pascal") { - return compile_pas(name); - } - - RunCompilerResult res = RunCompilerResult::failed_result(); - res.info = "This language is not supported yet."; - return res; -} - -RunCompilerResult compile_c_with_implementer(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/gcc", "-o", name.c_str(), "implementer.c", "-x", "c", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL); -} -RunCompilerResult compile_pas_with_implementer(const string &name, const string &path = work_path) { - executef("cp %s %s", (path + "/" + name + ".code").c_str(), (path + "/" + conf_str(name + "_unit_name") + ".pas").c_str()); - return run_compiler(path.c_str(), - "/usr/bin/fpc", "implementer.pas", ("-o" + name).c_str(), "-O2", NULL); -} -RunCompilerResult compile_cpp_with_implementer(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "implementer.cpp", "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", NULL); -} -RunCompilerResult compile_cpp11_with_implementer(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/g++", "-o", name.c_str(), "implementer.cpp", "-x", "c++", (name + ".code").c_str(), "-lm", "-O2", "-DONLINE_JUDGE", "-std=c++11", NULL); -} -/* -RunCompilerResult compile_python2(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/python2", "-E", "-s", "-B", "-O", "-c", - ("import py_compile\nimport sys\ntry:\n py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n sys.exit(0)\nexcept Exception as e:\n print e\n sys.exit(1)").c_str(), NULL); -} -RunCompilerResult compile_python3(const string &name, const string &path = work_path) { - return run_compiler(path.c_str(), - "/usr/bin/python3", "-I", "-B", "-O", "-c", ("import py_compile\nimport sys\ntry:\n py_compile.compile('" + name + ".code'" + ", '" + name + "', doraise=True)\n sys.exit(0)\nexcept Exception as e:\n print(e)\n sys.exit(1)").c_str(), NULL); -} -*/ -RunCompilerResult compile_with_implementer(const char *name) { - string lang = conf_str(string(name) + "_language"); - - if (has_illegal_keywords_in_file(work_path + "/" + name + ".code")) - { - RunCompilerResult res; - res.type = RS_DGS; - res.ust = -1; - res.usm = -1; - res.succeeded = false; - res.info = "Compile Failed"; - return res; - } - - if (lang == "C++") { - return compile_cpp_with_implementer(name); - } - if (lang == "C++11") { - return compile_cpp11_with_implementer(name); - } - if (lang == "C") { - return compile_c_with_implementer(name); - } - if (lang == "Pascal") { - return compile_pas_with_implementer(name); - } - - RunCompilerResult res = RunCompilerResult::failed_result(); - res.info = "This language is not supported yet."; - return res; +run_compiler_result compile(const string &name) { + string lang = conf_str(name + "_language", "auto"); + runp::config rpc(main_path + "/run/compile", + {"--custom", main_path + "/run/runtime", "--lang", lang, name}); + rpc.limits = RL_COMPILER_DEFAULT; + return run_compiler(rpc); +} + +run_compiler_result compile_with_implementer(const string &name, + const string &implementer = "implementer") { + string lang = conf_str(name + "_language", "auto"); + if (conf_has(name + "_unit_name")) { + file_put_contents(work_path + "/" + name + ".unit_name", conf_str(name + "_unit_name")); + } + runp::config rpc(main_path + "/run/compile", {"--custom", main_path + "/run/runtime", "--impl", + implementer, "--lang", lang, name}); + rpc.limits = RL_COMPILER_DEFAULT; + return run_compiler(rpc); +} + +run_compiler_result compile_submission_program(const string &name) { + return !conf_is("with_implementer", "on") ? compile(name) : compile_with_implementer(name); } /*====================== compile End ================== */ @@ -1300,277 +1377,420 @@ RunCompilerResult compile_with_implementer(const char *name) { /*====================== test ================== */ struct TestPointConfig { - int submit_answer; - - int validate_input_before_test; - string input_file_name; - string output_file_name; - string answer_file_name; - - TestPointConfig() - : submit_answer(-1), validate_input_before_test(-1) { - } - - void auto_complete(int num) { - if (submit_answer == -1) { - submit_answer = conf_is("submit_answer", "on"); - } - if (validate_input_before_test == -1) { - validate_input_before_test = conf_is("validate_input_before_test", "on"); - } - if (input_file_name.empty()) { - input_file_name = data_path + "/" + conf_input_file_name(num); - } - if (output_file_name.empty()) { - output_file_name = work_path + "/" + conf_output_file_name(num); - } - if (answer_file_name.empty()) { - answer_file_name = data_path + "/" + conf_output_file_name(num); - } - } + int submit_answer = -1; + int validate_input_before_test = -1; + int disable_program_input = -1; + string input_file_name; + string output_file_name; + string answer_file_name; + RunLimit limit = RunLimit(-1, -1, -1); + string checker; + + void auto_complete(int num) { + if (submit_answer == -1) { + submit_answer = conf_is("submit_answer", "on"); + } + if (validate_input_before_test == -1) { + validate_input_before_test = conf_is("validate_input_before_test", "on"); + } + if (disable_program_input == -1) { + disable_program_input = conf_str("disable_program_input", num, "off") == "on"; + } + if (input_file_name.empty()) { + input_file_name = data_path + "/" + conf_input_file_name(num); + } + if (output_file_name.empty()) { + output_file_name = work_path + "/" + conf_output_file_name(num); + } + if (answer_file_name.empty()) { + answer_file_name = data_path + "/" + conf_output_file_name(num); + } + if (limit.time == -1) { + limit = conf_run_limit(num, RL_DEFAULT); + } + if (checker.empty()) { + checker = conf_str("checker"); + } + } }; PointInfo test_point(const string &name, const int &num, TestPointConfig tpc = TestPointConfig()) { - tpc.auto_complete(num); - - if (tpc.validate_input_before_test) { - RunValidatorResult val_ret = run_validator( - tpc.input_file_name, - conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), - conf_str("validator")); - if (val_ret.type != RS_AC) { - return PointInfo(num, 0, -1, -1, - "Validator " + info_str(val_ret.type), - file_preview(tpc.input_file_name), "", - ""); - } else if (!val_ret.succeeded) { - return PointInfo(num, 0, -1, -1, - "Invalid Input", - file_preview(tpc.input_file_name), "", - val_ret.info); - } - } - - if (!conf_is("interaction_mode", "on")) { - RunResult pro_ret; - if (!tpc.submit_answer) { - pro_ret = run_submission_program( - tpc.input_file_name.c_str(), - tpc.output_file_name.c_str(), - conf_run_limit(num, RL_DEFAULT), - name); - if (conf_has("token")) { - file_hide_token(tpc.output_file_name, conf_str("token", "")); - } - if (pro_ret.type != RS_AC) { - return PointInfo(num, 0, -1, -1, - info_str(pro_ret.type), - file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), - ""); - } - } else { - pro_ret.type = RS_AC; - pro_ret.ust = -1; - pro_ret.usm = -1; - pro_ret.exit_code = 0; - } - - RunCheckerResult chk_ret = run_checker( - conf_run_limit("checker", num, RL_CHECKER_DEFAULT), - conf_str("checker"), - tpc.input_file_name, - tpc.output_file_name, - tpc.answer_file_name); - if (chk_ret.type != RS_AC) { - return PointInfo(num, 0, -1, -1, - "Checker " + info_str(chk_ret.type), - file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), - ""); - } - - return PointInfo(num, chk_ret.scr, pro_ret.ust, pro_ret.usm, - "default", - file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), - chk_ret.info); - } else { - string real_output_file_name = tpc.output_file_name + ".real_input.txt"; - string real_input_file_name = tpc.output_file_name + ".real_output.txt"; - RunSimpleInteractionResult rires = run_simple_interation( - tpc.input_file_name, - tpc.answer_file_name, - real_input_file_name, - real_output_file_name, - conf_run_limit(num, RL_DEFAULT), - conf_run_limit("interactor", num, RL_INTERACTOR_DEFAULT), - name); - - if (rires.ires.type != RS_AC) { - return PointInfo(num, 0, -1, -1, - "Interactor " + info_str(rires.ires.type), - file_preview(real_input_file_name), file_preview(real_output_file_name), - ""); - } - if (rires.res.type != RS_AC) { - return PointInfo(num, 0, -1, -1, - info_str(rires.res.type), - file_preview(real_input_file_name), file_preview(real_output_file_name), - ""); - } - - return PointInfo(num, rires.ires.scr, rires.res.ust, rires.res.usm, - "default", - file_preview(real_input_file_name), file_preview(real_output_file_name), - rires.ires.info); - } + tpc.auto_complete(num); + + if (tpc.validate_input_before_test) { + RunValidatorResult val_ret = + run_validator(tpc.input_file_name, conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), + conf_str("validator")); + if (val_ret.type != runp::RS_AC) { + return PointInfo(num, 0, -1, -1, "Validator " + info_str(val_ret.type), + file_preview(tpc.input_file_name), "", ""); + } else if (!val_ret.succeeded) { + return PointInfo(num, 0, -1, -1, "Invalid Input", file_preview(tpc.input_file_name), "", + val_ret.info); + } + } + + if (!conf_is("interaction_mode", "on")) { + RunResult pro_ret; + if (!tpc.submit_answer) { + pro_ret = run_submission_program( + tpc.disable_program_input ? "/dev/null" : tpc.input_file_name.c_str(), + tpc.output_file_name.c_str(), tpc.limit, name); + if (conf_has("token")) { + file_hide_token(tpc.output_file_name, conf_str("token", "")); + } + if (pro_ret.type != runp::RS_AC) { + return PointInfo(num, 0, -1, -1, info_str(pro_ret.type), + file_preview(tpc.input_file_name), + file_preview(tpc.output_file_name), ""); + } + } else { + pro_ret.type = runp::RS_AC; + pro_ret.ust = -1; + pro_ret.usm = -1; + pro_ret.exit_code = 0; + } + + if (tpc.checker == "nonempty") { + string usrout = file_preview(tpc.output_file_name); + if (usrout == "") { + return PointInfo(num, 0, -1, -1, "default", file_preview(tpc.input_file_name), + usrout, "wrong answer empty file\n"); + } else { + return PointInfo(num, 100, -1, -1, "default", file_preview(tpc.input_file_name), + usrout, "ok nonempty file\n"); + } + } else { + RunCheckerResult chk_ret = + run_checker(conf_run_limit("checker", num, RL_CHECKER_DEFAULT), tpc.checker, + tpc.input_file_name, tpc.output_file_name, tpc.answer_file_name); + if (chk_ret.type != runp::RS_AC) { + return PointInfo(num, 0, -1, -1, "Checker " + info_str(chk_ret.type), + file_preview(tpc.input_file_name), + file_preview(tpc.output_file_name), ""); + } + return PointInfo(num, chk_ret.scr, pro_ret.ust, pro_ret.usm, "default", + file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), + chk_ret.info); + } + } else { + string real_output_file_name = tpc.output_file_name + ".real_input.txt"; + string real_input_file_name = tpc.output_file_name + ".real_output.txt"; + RunSimpleInteractionResult rires = run_simple_interaction( + tpc.input_file_name, tpc.answer_file_name, real_input_file_name, real_output_file_name, + tpc.limit, conf_run_limit("interactor", num, RL_INTERACTOR_DEFAULT), name); + + if (rires.ires.type != runp::RS_AC) { + return PointInfo(num, 0, -1, -1, "Interactor " + info_str(rires.ires.type), + file_preview(real_input_file_name), + file_preview(real_output_file_name), ""); + } + if (rires.res.type != runp::RS_AC) { + return PointInfo(num, 0, -1, -1, info_str(rires.res.type), + file_preview(real_input_file_name), + file_preview(real_output_file_name), ""); + } + + return PointInfo(num, rires.ires.scr, rires.res.ust, rires.res.usm, "default", + file_preview(real_input_file_name), file_preview(real_output_file_name), + rires.ires.info); + } } PointInfo test_hack_point(const string &name, TestPointConfig tpc) { - tpc.submit_answer = false; - tpc.validate_input_before_test = false; - tpc.auto_complete(0); - RunValidatorResult val_ret = run_validator( - tpc.input_file_name, - conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), - conf_str("validator")); - if (val_ret.type != RS_AC) { - return PointInfo(0, 0, -1, -1, - "Validator " + info_str(val_ret.type), - file_preview(tpc.input_file_name), "", - ""); - } else if (!val_ret.succeeded) { - return PointInfo(0, 0, -1, -1, - "Invalid Input", - file_preview(tpc.input_file_name), "", - val_ret.info); - } - - RunLimit default_std_run_limit = conf_run_limit(0, RL_DEFAULT); - - prepare_run_standard_program(); - if (!conf_is("interaction_mode", "on")) { - RunProgramConfig rpc; - rpc.result_file_name = result_path + "/run_standard_program.txt"; - RunResult std_ret = run_submission_program( - tpc.input_file_name, - tpc.answer_file_name, - conf_run_limit("standard", 0, default_std_run_limit), - "std", - rpc); - if (std_ret.type != RS_AC) { - return PointInfo(0, 0, -1, -1, - "Standard Program " + info_str(std_ret.type), - file_preview(tpc.input_file_name), "", - ""); - } - if (conf_has("token")) { - file_hide_token(tpc.answer_file_name, conf_str("token", "")); - } - } else { - RunProgramConfig rpc; - rpc.result_file_name = result_path + "/run_standard_program.txt"; - string real_output_file_name = tpc.answer_file_name; - string real_input_file_name = tpc.output_file_name + ".real_output.txt"; - RunSimpleInteractionResult rires = run_simple_interation( - tpc.input_file_name, - tpc.answer_file_name, - real_input_file_name, - real_output_file_name, - conf_run_limit("standard", 0, default_std_run_limit), - conf_run_limit("interactor", 0, RL_INTERACTOR_DEFAULT), - "std", - rpc); - - if (rires.ires.type != RS_AC) { - return PointInfo(0, 0, -1, -1, - "Interactor " + info_str(rires.ires.type) + " (Standard Program)", - file_preview(real_input_file_name), "", - ""); - } - if (rires.res.type != RS_AC) { - return PointInfo(0, 0, -1, -1, - "Standard Program " + info_str(rires.res.type), - file_preview(real_input_file_name), "", - ""); - } - } - - PointInfo po = test_point(name, 0, tpc); - po.scr = po.scr != 100; - return po; -} - -CustomTestInfo ordinary_custom_test(const string &name) { - RunLimit lim = conf_run_limit(0, RL_DEFAULT); - lim.time += 2; - - string input_file_name = work_path + "/input.txt"; - string output_file_name = work_path + "/output.txt"; - - RunResult pro_ret = run_submission_program( - input_file_name, - output_file_name, - lim, - name); - if (conf_has("token")) { - file_hide_token(output_file_name, conf_str("token", "")); - } - string info; - if (pro_ret.type == RS_AC) { - info = "Success"; - } else { - info = info_str(pro_ret.type); - } - string exp; - if (pro_ret.type == RS_TLE) { - exp = "

[time limit: " + vtos(lim.time) + "s]

"; - } - return CustomTestInfo(pro_ret.ust, pro_ret.usm, - info, exp, file_preview(output_file_name, 2048)); -} - -int scale_score(int scr100, int full) { - return scr100 * full / 100; + tpc.submit_answer = false; + tpc.validate_input_before_test = false; + tpc.auto_complete(0); + RunValidatorResult val_ret = + run_validator(tpc.input_file_name, conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), + conf_str("validator")); + if (val_ret.type != runp::RS_AC) { + return PointInfo(0, 0, -1, -1, "Validator " + info_str(val_ret.type), + file_preview(tpc.input_file_name), "", ""); + } else if (!val_ret.succeeded) { + return PointInfo(0, 0, -1, -1, "Invalid Input", file_preview(tpc.input_file_name), "", + val_ret.info); + } + + RunLimit default_std_run_limit = conf_run_limit(0, RL_DEFAULT); + + prepare_run_standard_program(); + if (!conf_is("interaction_mode", "on")) { + RunProgramConfig rpc; + rpc.result_file_name = result_path + "/run_standard_program.txt"; + RunResult std_ret = run_submission_program( + tpc.input_file_name, tpc.answer_file_name, + conf_run_limit("standard", 0, default_std_run_limit), "std", rpc); + if (std_ret.type != runp::RS_AC) { + return PointInfo(0, 0, -1, -1, "Standard Program " + info_str(std_ret.type), + file_preview(tpc.input_file_name), "", ""); + } + if (conf_has("token")) { + file_hide_token(tpc.answer_file_name, conf_str("token", "")); + } + } else { + RunProgramConfig rpc; + rpc.result_file_name = result_path + "/run_standard_program.txt"; + string real_output_file_name = tpc.answer_file_name; + string real_input_file_name = tpc.output_file_name + ".real_output.txt"; + RunSimpleInteractionResult rires = run_simple_interaction( + tpc.input_file_name, tpc.answer_file_name, real_input_file_name, real_output_file_name, + conf_run_limit("standard", 0, default_std_run_limit), + conf_run_limit("interactor", 0, RL_INTERACTOR_DEFAULT), "std", rpc); + + if (rires.ires.type != runp::RS_AC) { + return PointInfo(0, 0, -1, -1, + "Interactor " + info_str(rires.ires.type) + " (Standard Program)", + file_preview(real_input_file_name), "", ""); + } + if (rires.res.type != runp::RS_AC) { + return PointInfo(0, 0, -1, -1, "Standard Program " + info_str(rires.res.type), + file_preview(real_input_file_name), "", ""); + } + } + + PointInfo po = test_point(name, 0, tpc); + po.scr = po.scr != 100; + return po; +} + +struct CustomTestConfig { + RunLimit base_limit = conf_run_limit(0, RL_DEFAULT); +}; + +CustomTestInfo ordinary_custom_test(const string &name, CustomTestConfig ctc = CustomTestConfig()) { + RunLimit lim = ctc.base_limit; + lim.time += 2; + + string input_file_name = work_path + "/input.txt"; + string output_file_name = work_path + "/output.txt"; + + RunResult pro_ret = run_submission_program(input_file_name, output_file_name, lim, name); + if (conf_has("token")) { + file_hide_token(output_file_name, conf_str("token", "")); + } + string info; + if (pro_ret.type == runp::RS_AC) { + info = "Success"; + } else { + info = info_str(pro_ret.type); + } + string exp; + if (pro_ret.type == runp::RS_TLE) { + exp = "

[time limit: " + vtos(lim.time) + "s]

"; + } + return CustomTestInfo(pro_ret.ust, pro_ret.usm, info, exp, + file_preview(output_file_name, 2048)); +} + +// classifications of tests +// primary tests = main tests + extra tests +// sample test data points are usually some of the extra test data points + +struct PrimaryDataTestConfig { + bool disable_ex_tests = false; +}; + +template +bool main_data_test(TP test_point_func) { + int n = conf_int("n_tests", 10); + int nT = conf_int("n_subtasks", 0); + + bool passed = true; + if (nT == 0) { // OI + map point_scores; + score_t remaining_tests_total_score = 100; + int remaining_tests_cnt = n; + + for (int i = 1; i <= n; i++) { + score_t point_score = conf_score("point_score", i, -1); + + if (point_score != -1) { + point_scores[i] = point_score; + remaining_tests_total_score -= point_score; + remaining_tests_cnt--; + } + } + + for (int i = 1; i <= n; i++) { + report_judge_status_f("Judging Test #%d", i); + PointInfo po = test_point_func(i); + if (po.scr != 100) { + passed = false; + } + + if (point_scores.count(i)) { + po.scr = scale_score(po.scr, point_scores[i]); + } else { + po.scr = scale_score(po.scr, remaining_tests_total_score / remaining_tests_cnt); + } + + add_point_info(po); + } + } else if (nT == 1 && conf_subtask_meta_info(1).is_ordinary()) { // ACM + for (int i = 1; i <= n; i++) { + report_judge_status_f("Judging Test #%d", i); + PointInfo po = test_point_func(i); + if (po.scr != 100) { + passed = false; + po.scr = i == 1 ? 0 : -100; + add_point_info(po); + break; + } else { + po.scr = i == 1 ? 100 : 0; + add_point_info(po); + } + } + } else { // subtask + map subtask_metas; + score_t remaining_subtasks_total_score = 100; + int remaining_subtasks_cnt = nT; + + for (int t = 1; t <= nT; t++) { + score_t subtask_score = conf_score("subtask_score", t, -1); + + subtask_metas[t] = conf_subtask_meta_info(t); + subtask_metas[t].full_score = subtask_score; + + if (subtask_score != -1) { + remaining_subtasks_total_score -= subtask_score; + remaining_subtasks_cnt--; + } + } + + map subtasks; + for (int t = 1; t <= nT; t++) { + if (subtask_metas[t].full_score == -1) { + subtask_metas[t].full_score = + remaining_subtasks_total_score / remaining_subtasks_cnt; + } + + SubtaskInfo st_info(subtask_metas[t]); + + if (!st_info.resolve_dependencies(subtasks)) { + st_info.info = "Skipped"; + } else { + for (int i : st_info.meta.points_id) { + report_judge_status_f("Judging Test #%d of Subtask #%d", i, t); + PointInfo po = test_point_func(i); + st_info.add_point_info(po); + if (st_info.early_stop) { + break; + } + } + } + + subtasks[t] = st_info; + passed = passed && st_info.passed; + add_subtask_info(st_info); + } + } + if (passed) { + tot_score = 100; + } + return passed; +} + +template +bool ex_data_test(TP test_point_func) { + int m = conf_int("n_ex_tests", 0); + if (m > 0) { + for (int i = 1; i <= m; i++) { + report_judge_status_f("Judging Extra Test #%d", i); + PointInfo po = test_point_func(-i); + if (po.scr != 100) { + po.num = -1; + po.info = "Extra Test Failed : " + po.info + " on " + vtos(i); + po.scr = -3; + add_point_info(po); + return false; + } + } + PointInfo po(-1, 0, -1, -1, "Extra Test Passed", "", "", ""); + add_point_info(po); + } + return true; +} + +template +bool primary_data_test(TP test_point_func, + const PrimaryDataTestConfig &pdtc = PrimaryDataTestConfig()) { + bool passed = main_data_test(test_point_func); + if (passed && !pdtc.disable_ex_tests) { + passed = ex_data_test(test_point_func); + } + return passed; +} + +template +bool sample_data_test(TP test_point_func) { + int n = conf_int("n_sample_tests", 0); + bool passed = true; + for (int i = 1; i <= n; i++) { + report_judge_status_f("Judging Sample Test #%d", i); + PointInfo po = test_point_func(-i); + po.num = i; + if (po.scr != 100) { + passed = false; + } + po.scr = scale_score(po.scr, 100 / n); + add_point_info(po); + } + if (passed) { + tot_score = 100; + } + return passed; } /*====================== test End ================== */ /*======================= conf init =================== */ -void main_judger_init(int argc, char **argv) { - main_path = UOJ_WORK_PATH; - work_path = main_path + "/work"; - result_path = string(UOJ_RESULT_PATH); - load_config(work_path + "/submission.conf"); - problem_id = conf_int("problem_id"); - data_path = string(UOJ_DATA_PATH) + "/" + conf_str("problem_id"); - load_config(data_path + "/problem.conf"); - - executef("cp %s/require/* %s 2>/dev/null", data_path.c_str(), work_path.c_str()); - - if (conf_is("use_builtin_judger", "on")) { - config["judger"] = string(UOJ_WORK_PATH) + "/builtin/judger/judger"; - } else { - config["judger"] = data_path + "/judger"; - } +void main_judger_init(int argc, char **argv) { + cerr << "main_judger_init is deprecated. use uoj_judger_v2 instead!" << endl; + exit(1); } void judger_init(int argc, char **argv) { - if (argc != 5) { - exit(1); - } - main_path = argv[1]; - work_path = argv[2]; - result_path = argv[3]; - data_path = argv[4]; - load_config(work_path + "/submission.conf"); - problem_id = conf_int("problem_id"); - load_config(data_path + "/problem.conf"); - - if (config.count("use_builtin_checker")) { - config["checker"] = main_path + "/builtin/checker/" + config["use_builtin_checker"]; - } else { - config["checker"] = data_path + "/chk"; - } - config["validator"] = data_path + "/val"; + if (argc != 5) { + cerr << "judger: argc != 5" << endl; + exit(1); + } + main_path = argv[1]; + work_path = argv[2]; + result_path = argv[3]; + data_path = argv[4]; + load_config(work_path + "/submission.conf"); + problem_id = conf_int("problem_id"); + load_config(data_path + "/problem.conf"); + runp::run_path = main_path + "/run"; + + PointInfo::show_in = conf_str("show_in", "on") == "on"; + PointInfo::show_out = conf_str("show_out", "on") == "on"; + PointInfo::show_res = conf_str("show_res", "on") == "on"; + + string score_type = conf_str("score_type", "int"); + if (score_type == "int") { + score_t::mode = SM_INT; + } else { + score_t::mode = SM_REAL; + sscanf(score_type.c_str(), "real-%d", &score_t::D); + score_t::P = 1; + for (int i = 0; i < score_t::D; i++) { + score_t::P *= 10; + } + } + + if (chdir(work_path.c_str()) != 0) { + cerr << "invalid work path" << endl; + exit(1); + } + + if (config.count("use_builtin_checker")) { + config["checker"] = main_path + "/builtin/checker/" + config["use_builtin_checker"]; + } else { + config["checker"] = data_path + "/chk"; + } + config["validator"] = data_path + "/val"; } /*===================== conf init End ================= */ diff --git a/judger/uoj_judger/include/uoj_judger_v2.h b/judger/uoj_judger/include/uoj_judger_v2.h new file mode 100644 index 000000000..cf5a5ca22 --- /dev/null +++ b/judger/uoj_judger/include/uoj_judger_v2.h @@ -0,0 +1,2457 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uoj_run.h" +#include "uoj_secure.h" + +namespace fs = std::filesystem; +using namespace std; + +/*========================== string ====================== */ + +inline string htmlspecialchars(const string &s) { + string r; + for (int i = 0; i < (int)s.length(); i++) { + switch (s[i]) { + case '&': + r += "&"; + break; + case '<': + r += "<"; + break; + case '>': + r += ">"; + break; + case '"': + r += """; + break; + case '\0': + r += "\\0"; + break; + default: + r += s[i]; + break; + } + } + return r; +} + +template +inline string list_to_string(const T &list) { + ostringstream sout; + sout << "{"; + bool is_first = false; + for (auto &t : list) { + if (!is_first) { + sout << ", "; + } + sout << t; + } + sout << "}"; + return sout.str(); +} + +// trim from start (in place) +inline void ltrim(std::string &s) { + s.erase(s.begin(), + std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); })); +} + +// trim from end (in place) +inline void rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }) + .base(), + s.end()); +} + +// trim from both ends (in place) +inline void trim(std::string &s) { + rtrim(s); + ltrim(s); +} + +// trim from start (copying) +inline std::string ltrim_copy(std::string s) { + ltrim(s); + return s; +} + +// trim from end (copying) +inline std::string rtrim_copy(std::string s) { + rtrim(s); + return s; +} + +// trim from both ends (copying) +inline std::string trim_copy(std::string s) { + trim(s); + return s; +} + +/*========================== random ====================== */ + +inline string gen_token() { + u64 seed = time(NULL); + FILE *f = fopen("/dev/urandom", "r"); + if (f) { + for (int i = 0; i < 8; i++) seed = seed << 8 | (u8)fgetc(f); + fclose(f); + } + uoj_mt_rand_engine rnd(seed); + return rnd.randstr(64); +} + +/*========================== crypto ====================== */ + +inline string file_get_contents(const string &name) { + string out; + FILE *f = fopen(name.c_str(), "r"); + if (!f) { + return out; + } + + const int BUFFER_SIZE = 1024; + u8 buffer[BUFFER_SIZE + 1]; + while (!feof(f)) { + int ret = fread(buffer, 1, BUFFER_SIZE, f); + if (ret < 0) { + break; + } + out.append((char *)buffer, ret); + } + fclose(f); + return out; +} +inline bool file_put_contents(const string &name, const string &m) { + FILE *f = fopen(name.c_str(), "w"); + if (!f) { + return false; + } + int c = fwrite(m.data(), 1, m.length(), f); + fclose(f); + return c == (int)m.length(); +} + +inline bool file_encrypt(const string &fi, const string &fo, const string &key) { + string m = file_get_contents(fi); + uoj_cipher cipher(key); + cipher.encrypt(m); + return file_put_contents(fo, m); +} +inline bool file_decrypt(const string &fi, const string &fo, const string &key) { + string m = file_get_contents(fi); + uoj_cipher cipher(key); + if (cipher.decrypt(m)) { + file_put_contents(fo, m); + return true; + } else { + file_put_contents(fo, "Unauthorized output"); + return false; + } +} + +/*========================= file ====================== */ + +string file_preview(const string &name, const int &len = 100) { + FILE *f = fopen(name.c_str(), "r"); + if (f == NULL) { + return ""; + } + + struct stat stat_buf; + stat(name.c_str(), &stat_buf); + + string res = ""; + if (len == -1) { + int c; + while (c = fgetc(f), c != EOF) { + res += c; + } + } else { + int c; + while (c = fgetc(f), c != EOF && (int)res.size() < len + 4) { + res += c; + } + if ((int)res.size() > len + 3) { + int omitted = (int)stat_buf.st_size - len; + res.resize(len); + res += "\n\n(" + to_string(omitted) + " bytes omitted)"; + } + } + fclose(f); + return res; +} +/* +void file_hide_token(const string &name, const string &token) { + executef("cp %s %s.bak", name.c_str(), name.c_str()); + + FILE *rf = fopen((name + ".bak").c_str(), "r"); + FILE *wf = fopen(name.c_str(), "w"); + int c; + for (int i = 0; i <= (int)token.length(); i++) + { + c = fgetc(rf); + if (c != (i < (int)token.length() ? token[i] : '\n')) + { + fprintf(wf, "Unauthorized output\n"); + fclose(rf); + fclose(wf); + return; + } + } + while (c = fgetc(rf), c != EOF) { + fputc(c, wf); + } + fclose(rf); + fclose(wf); +}*/ +int file_hide_token(const string &token, const string &in, const string &out) { + ifstream fin(in); + ofstream fout(out); + + if (!fin || !fout) { + return -1; + } + + string target = token + "\n"; + int L = max(1 << 15, (int)target.length()); + char buf[L]; + + fin.read(buf, target.length()); + if (fin.bad()) { + return -1; + } + if (!(fin.good() && equal(target.begin(), target.end(), buf))) { + fout << "???" << endl; + return 1; + } + + while (!fin.eof()) { + fin.read(buf, L); + fout.write(buf, fin.gcount()); + if (fin.bad()) { + return -1; + } + } + return 0; +} +bool file_replace_tokens(const string &name, const string &token, const string &new_token) { + string esc_name = escapeshellarg(name); + string esc_token = escapeshellarg(token); + string esc_new_token = escapeshellarg(new_token); + return executef("sed -i s/%s/%s/g %s", esc_token.c_str(), esc_new_token.c_str(), + esc_name.c_str()) + == 0; +} +bool file_copy(const string &a, const string &b) { // copy a to b + string esc_a = escapeshellarg(a); + string esc_b = escapeshellarg(b); + return executef("cp %s %s -f 2>/dev/null", esc_a.c_str(), esc_b.c_str()) + == 0; // the most cubao implementation in the world +} +bool file_hardlink(const string &a, const string &b) { // copy a to b + string esc_a = escapeshellarg(a); + string esc_b = escapeshellarg(b); + return executef("cp %s %s -lf 2>/dev/null", esc_a.c_str(), esc_b.c_str()) + == 0; // the most cubao implementation in the world +} +bool file_move(const string &a, const string &b) { // move a to b + string esc_a = escapeshellarg(a); + string esc_b = escapeshellarg(b); + return executef("mv %s %s", esc_a.c_str(), esc_b.c_str()) + == 0; // the most cubao implementation in the world +} + +/*======================= file End ==================== */ + +/*====================== parameter ==================== */ + +const runp::limits_t RL_DEFAULT(1, 256, 64); +const runp::limits_t RL_JUDGER_DEFAULT(600, 10 * 1024, 128); // 10GB. change it if needed +const runp::limits_t RL_CHECKER_DEFAULT(5, 256, 64); +const runp::limits_t RL_INTERACTOR_DEFAULT(1, 256, 64); +const runp::limits_t RL_VALIDATOR_DEFAULT(5, 256, 64); +const runp::limits_t RL_TRANSFORMER_DEFAULT(30, 512, 256); +const runp::limits_t RL_COMPILER_DEFAULT(15, 2048, 64); + +struct InfoBlock { + string title; + string content; + int orig_size; + + static InfoBlock empty(const string &title) { + InfoBlock info; + info.title = title; + info.content = ""; + info.orig_size = -1; + return info; + } + static InfoBlock from_string(const string &title, const string &content) { + InfoBlock info; + info.title = title; + info.content = content; + info.orig_size = -1; + return info; + } + static InfoBlock from_file(const string &title, const string &name, int preview_size = 100) { + InfoBlock info; + info.title = title; + info.content = file_preview(name, preview_size); + info.orig_size = -1; + return info; + } + static InfoBlock from_file_with_size(const string &title, const string &name, + int preview_size = 100) { + InfoBlock info; + info.title = title; + info.content = file_preview(name, preview_size); + info.orig_size = fs::file_size(name); + return info; + } + + string to_str() const { + if (title == "res") { + return "" + htmlspecialchars(content) + ""; + } + + string str = ""; + + return str; + } +}; + +struct PointInfo { + int num; + int scr = 0; + int ust = -1, usm = -1; + string info, res; + + vector li; + + static PointInfo extra_test_passed() { + PointInfo po; + po.num = -1; + po.info = "Extra Test Passed"; + return po; + } + + void set_info(const string &info) { + if (info == "default") { + if (scr == 0) { + this->info = "Wrong Answer"; + } else if (scr == 100) { + this->info = "Accepted"; + } else { + this->info = "Acceptable Answer"; + } + } else { + this->info = info; + } + } +}; + +/*struct CustomTestInfo { + int ust, usm; + string info, exp, out; + + CustomTestInfo(const int &_ust, const int &_usm, const string &_info, + const string &_exp, const string &_out) + : ust(_ust), usm(_usm), info(_info), + exp(_exp), out(_out) { + } +};*/ + +struct run_checker_result { + runp::RS_TYPE type; + int scr; + string info; + + static run_checker_result from_file(const string &file_name, const runp::result &rres) { + run_checker_result res; + res.type = rres.type; + + if (rres.type != runp::RS_AC) { + res.scr = 0; + } else { + FILE *fres = fopen(file_name.c_str(), "r"); + char type[21]; + if (fres == NULL || fscanf(fres, "%20s", type) != 1) { + return run_checker_result::failed_result(); + } + if (strcmp(type, "ok") == 0) { + res.scr = 100; + } else if (strcmp(type, "points") == 0) { + double d; + if (fscanf(fres, "%lf", &d) != 1) { + return run_checker_result::failed_result(); + } else { + res.scr = (int)floor(100 * d + 0.5); + } + } else { + res.scr = 0; + } + fclose(fres); + } + res.info = file_preview(file_name); + return res; + } + + static run_checker_result failed_result() { + run_checker_result res; + res.type = runp::RS_JGF; + res.scr = 0; + res.info = "Checker Failed"; + return res; + } +}; +struct run_validator_result { + runp::RS_TYPE type; + bool succeeded; + string info; + + static run_validator_result failed_result() { + run_validator_result res; + res.type = runp::RS_JGF; + res.succeeded = 0; + res.info = "Validator Failed"; + return res; + } +}; +struct run_compiler_result { + runp::RS_TYPE type; + bool succeeded; + string info; + + static run_compiler_result failed_result() { + run_compiler_result res; + res.type = runp::RS_JGF; + res.succeeded = false; + res.info = "Compile Failed"; + return res; + } +}; +struct run_transformer_result { + runp::RS_TYPE type; + bool succeeded; + string info; +}; + +// see also: run_simple_interaction +struct run_simple_interaction_result { + runp::result res; // prog + run_checker_result ires; // interactor +}; + +fs::path main_path; +fs::path work_path; +fs::path data_path; +fs::path result_path; + +int tot_time = 0; +int max_memory = 0; +int tot_score = 0; +string uoj_errcode; // empty means no error +ostringstream details_out; +map uconfig; + +void uoj_error(const string &_uoj_errcode) { + if (uoj_errcode.empty()) { + uoj_errcode = _uoj_errcode; + } +} + +class jgf_error : exception { +public: + string code; + +protected: + string info; + +public: + explicit jgf_error(const string &code, const string &info = "") : code(code), info(info) { + if (this->info.empty()) { + this->info = runp::rstype_str(runp::RS_JGF); + } + } + + virtual const char *what() const noexcept override { + return this->info.c_str(); + } +}; + +/*==================== parameter End ================== */ + +/*====================== config set =================== */ + +void print_config() { + for (auto &p : uconfig) { + cerr << p.first << " = " << p.second << endl; + } +} +void load_config(const string &filename) { + ifstream fin(filename.c_str()); + + if (!fin) { + return; + } + + string key, val; + + // the first token of a line is key, the rest is value + while (fin >> key) { + getline(fin, val); + uconfig[key] = trim_copy(val); + } + + // while (fin >> key >> val) { + // uconfig[key] = val; + // } +} +string conf_str(const string &key, int num, const string &val) { + ostringstream sout; + sout << key << "_" << num; + if (uconfig.count(sout.str()) == 0) { + return val; + } + return uconfig[sout.str()]; +} +string conf_str(const string &key, const string &val) { + if (uconfig.count(key) == 0) { + return val; + } + return uconfig[key]; +} +string conf_str(const string &key) { + return conf_str(key, ""); +} +int conf_int(const string &key, const int &val) { + if (uconfig.count(key) == 0) { + return val; + } + return atoi(uconfig[key].c_str()); +} +int conf_int(const string &key, int num, const int &val) { + ostringstream sout; + sout << key << "_" << num; + if (uconfig.count(sout.str()) == 0) { + return conf_int(key, val); + } + return atoi(uconfig[sout.str()].c_str()); +} +int conf_int(const string &key) { + return conf_int(key, 0); +} +double conf_double(const string &key, const double &val) { + if (uconfig.count(key) == 0) { + return val; + } + return stod(uconfig[key]); +} +double conf_double(const string &key, int num, const double &val) { + ostringstream sout; + sout << key << "_" << num; + if (uconfig.count(sout.str()) == 0) { + return conf_double(key, val); + } + return stod(uconfig[sout.str()]); +} +double conf_double(const string &key) { + return conf_double(key, 0); +} +string conf_file_name_with_num(string s, int num) { + ostringstream name; + if (num < 0) { + name << "ex_"; + } + name << conf_str(s + "_pre", s) << abs(num) << "." << conf_str(s + "_suf", "txt"); + return name.str(); +} +string conf_input_file_name(int num) { + return conf_file_name_with_num("input", num); +} +string conf_output_file_name(int num) { + return conf_file_name_with_num("output", num); +} +runp::limits_t conf_run_limit(string pre, const int &num, const runp::limits_t &val) { + if (!pre.empty()) { + pre += "_"; + } + runp::limits_t limits; + limits.time = conf_double(pre + "time_limit", num, val.time); + limits.memory = conf_int(pre + "memory_limit", num, val.memory); + limits.output = conf_int(pre + "output_limit", num, val.output); + limits.real_time = conf_double(pre + "real_time_limit", num, val.real_time); + limits.stack = conf_int(pre + "stack_limit", num, val.real_time); + return limits; +} +runp::limits_t conf_run_limit(const int &num, const runp::limits_t &val) { + return conf_run_limit("", num, val); +} +void conf_add(const string &key, const string &val) { + if (uconfig.count(key)) { + return; + } + uconfig[key] = val; +} +bool conf_has(const string &key) { + return uconfig.count(key); +} +bool conf_is(const string &key, const string &val) { + return uconfig.count(key) && uconfig[key] == val; +} + +template +void map_add(map &m, const initializer_list> &vs) { + for (const auto &v : vs) { + if (!m.count(v.first)) { + m[v.first] = v.second; + } + } +} + +/*==================== config set End ================= */ + +/*====================== info print =================== */ + +void add_point_info(const PointInfo &info, bool update_tot_score = true) { + if (info.num >= 0) { + if (info.ust >= 0) { + tot_time += info.ust; + } + if (info.usm >= 0) { + max_memory = max(max_memory, info.usm); + } + } + if (update_tot_score) { + tot_score += info.scr; + } + + details_out << "" << endl; + + for (const InfoBlock &b : info.li) { + if (b.title == "input" && conf_str("show_in", "on") != "on") { + continue; + } + if (b.title == "output" && conf_str("show_out", "on") != "on") { + continue; + } + if (conf_str("show_" + b.title, "on") != "on") { + continue; + } + details_out << b.to_str() << endl; + } + if (conf_str("show_res", "on") == "on") { + details_out << "" << htmlspecialchars(info.res) << "" << endl; + } + details_out << "" << endl; +} +void add_custom_test_info(const PointInfo &info) { + if (info.ust >= 0) { + tot_time += info.ust; + } + if (info.usm >= 0) { + max_memory = max(max_memory, info.usm); + } + + details_out << "" << endl; + for (const InfoBlock &b : info.li) { + if (b.title == "input" && conf_str("show_in", "off") != "on") { + continue; + } + if (b.title == "output" && conf_str("show_out", "on") != "on") { + continue; + } + details_out << b.to_str() << endl; + } + if (conf_str("show_res", "on") == "on") { + details_out << "" << htmlspecialchars(info.res) << "" << endl; + } + details_out << "" << endl; +} +void add_subtask_info(const int &num, const int &scr, const string &info, + const vector &points) { + details_out << "" << endl; + tot_score += scr; + for (const PointInfo &point : points) { + add_point_info(point, false); + } + details_out << "" << endl; +} +[[noreturn]] void end_judge_ok() { + ofstream fres(result_path / "result.txt"); + if (uoj_errcode.empty()) { + fres << "score " << tot_score << "\n"; + fres << "time " << tot_time << "\n"; + fres << "memory " << max_memory << "\n"; + } else { + fres << "error Judgment Failed\n"; + } + fres << "details\n"; + if (uoj_errcode.empty()) { + fres << "\n"; + } else { + fres << "\n"; + } + fres << details_out.str(); + fres << "\n"; + fres.close(); + exit(uoj_errcode.empty() && fres ? 0 : 1); +} +[[noreturn]] void end_judge_judgment_failed(const string &info) { + ofstream fres(result_path / "result.txt"); + fres << "error Judgment Failed\n"; + fres << "details\n"; + fres << "" << htmlspecialchars(info) << "\n"; + fres.close(); + exit(0); +} +[[noreturn]] void end_judge_compile_error(const run_compiler_result &res) { + ofstream fres(result_path / "result.txt"); + fres << "error Compile Error\n"; + fres << "details\n"; + fres << "" << htmlspecialchars(res.info) << "\n"; + fres.close(); + exit(0); +} + +void report_judge_status(const char *status) { + FILE *f = fopen((result_path / "cur_status.txt").c_str(), "a"); + if (f == NULL) { + return; + } + if (flock(fileno(f), LOCK_EX) != -1) { + if (ftruncate(fileno(f), 0) != -1) { + fprintf(f, "%s\n", status); + fflush(f); + } + flock(fileno(f), LOCK_UN); + } + fclose(f); +} +bool report_judge_status_f(const char *fmt, ...) { + const int MaxL = 512; + char status[MaxL]; + va_list ap; + va_start(ap, fmt); + int res = vsnprintf(status, MaxL, fmt, ap); + if (res < 0 || res >= MaxL) { + return false; + } + report_judge_status(status); + va_end(ap); + return true; +} + +/*==================== info print End ================= */ + +/*========================== run ====================== */ + +// namespace for run_program +namespace runp { +// internal programs for run_program +namespace internal { +result nonempty(const config &rpc) { + result res; + res.ust = res.usm = 0; + if (rpc.rest_args.size() != 3) { + res.exit_code = -1; + res.type = RS_RE; + return res; + } + + ofstream ferr(rpc.error_file_name); + + if (fs::file_size(rpc.rest_args[1]) > 0) { + ferr << "ok nonempty file" << endl; + } else { + ferr << "wrong answer empty file" << endl; + } + res.exit_code = 0; + res.type = RS_AC; + return res; +} + +result cp(const config &rpc) { + result res; + res.ust = res.usm = 0; + if (rpc.rest_args.size() != 2) { + res.exit_code = -1; + res.type = RS_RE; + return res; + } + + if (file_hardlink(rpc.rest_args[0], rpc.rest_args[1]) + || file_copy(rpc.rest_args[0], rpc.rest_args[1])) { + res.exit_code = 0; + res.type = RS_AC; + } else { + res.exit_code = -1; + res.type = RS_RE; + } + return res; +} + +result hide_token(const config &rpc) { + result res; + res.ust = res.usm = 0; + if (rpc.rest_args.size() != 2) { + res.exit_code = -1; + res.type = RS_RE; + return res; + } + + int ret = file_hide_token(conf_str("token", ""), rpc.rest_args[0], rpc.rest_args[1]); + if (ret == -1) { + res.exit_code = -1; + res.type = RS_RE; + } else { + res.type = RS_AC; + res.exit_code = ret; + if (res.exit_code != 0) { + ofstream ferr(rpc.error_file_name); + ferr << "Invalid Output" << endl; + } + } + return res; +} + +map> call_table = { + {"nonempty", nonempty}, {"cp", cp}, {"hide_token", hide_token}}; +} // namespace internal + +result run(const config &rpc) { + if (rpc.type == "internal" && internal::call_table.count(rpc.program_name)) { + return internal::call_table[rpc.program_name](rpc); + } else if (rpc.program_name.empty()) { + throw jgf_error("RPFALEM"); // run_program failed, because program is empty + } + string cmd = rpc.get_cmd(); + if (execute(rpc.get_cmd()) != 0) { + uoj_error("RPFAL"); // run_program failed + return result::failed_result(); + } + return result::from_file(rpc.result_file_name); +} +} // namespace runp + +// for all run_xxx functions, rpc is passed +// rpc should set program name, args, type, limits, readable/writable files +// run_xxx will fill in in/out/err/res file names, and may reset args, readable files, etc. +run_validator_result run_validator(runp::config rpc, const string &input_file_name) { + rpc.result_file_name = result_path / "run_validator_result.txt"; + rpc.input_file_name = input_file_name; + rpc.output_file_name = "/dev/null"; + rpc.error_file_name = result_path / "validator_error.txt"; + runp::result ret = runp::run(rpc); + + run_validator_result res; + res.type = ret.type; + if (ret.type != runp::RS_AC || ret.exit_code != 0) { + res.succeeded = false; + res.info = file_preview(rpc.error_file_name); + } else { + res.succeeded = true; + } + return res; +} + +run_checker_result run_checker(runp::config rpc, const string &input_file_name, + const string &output_file_name, const string &answer_file_name) { + rpc.result_file_name = result_path / "run_checker_result.txt"; + rpc.input_file_name = "/dev/null"; + rpc.output_file_name = "/dev/null"; + rpc.error_file_name = result_path / "checker_error.txt"; + rpc.readable_file_names.insert(rpc.readable_file_names.end(), + {input_file_name, output_file_name, answer_file_name}); + rpc.rest_args = {fs::canonical(input_file_name), fs::canonical(output_file_name), + fs::canonical(answer_file_name)}; + return run_checker_result::from_file(rpc.error_file_name, runp::run(rpc)); +} + +template +run_compiler_result run_compiler(runp::config rpc) { + rpc.result_file_name = result_path / "run_compiler_result.txt"; + rpc.input_file_name = "/dev/null"; + rpc.output_file_name = "stderr"; + rpc.error_file_name = result_path / "compiler_result.txt"; + rpc.type = "compiler"; + + runp::result ret = runp::run(rpc); + run_compiler_result res; + res.type = ret.type; + res.succeeded = ret.type == runp::RS_AC && ret.exit_code == 0; + if (!res.succeeded) { + if (ret.type == runp::RS_AC) { + res.info = file_preview(result_path / "compiler_result.txt", 10240); + } else if (ret.type == runp::RS_JGF) { + res.info = "No Comment"; + } else { + res.info = "Compiler " + runp::rstype_str(ret.type); + } + } + return res; +} + +runp::result run_submission_program(runp::config rpc, const string &input_file_name, + const string &output_file_name) { + rpc.result_file_name = result_path / "run_submission_program.txt"; + rpc.input_file_name = input_file_name; + rpc.output_file_name = output_file_name; + rpc.error_file_name = "/dev/null"; + + runp::result res = runp::run(rpc); + if (res.type == runp::RS_AC && res.exit_code != 0) { + res.type = runp::RS_RE; + } + return res; +} + +void prepare_interactor() { + static bool prepared = false; + if (prepared) { + return; + } + string data_path_std = data_path / "interactor"; + string work_path_std = work_path / "interactor"; + executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str()); + conf_add("interactor_language", "C++17"); + prepared = true; +} + +// simple: prog <---> interactor <---> data +run_simple_interaction_result run_simple_interaction(const string &input_file_name, + const string &answer_file_name, + const string &real_input_file_name, + const string &real_output_file_name, + runp::config rpc, runp::config irpc) { + prepare_interactor(); + + rpc.result_file_name = result_path / "run_submission_program.txt"; + rpc.input_file_name = "stdin"; + rpc.output_file_name = "stdout"; + rpc.error_file_name = "/dev/null"; + + irpc.result_file_name = result_path / "run_interactor_program.txt"; + irpc.readable_file_names.push_back(input_file_name); + irpc.readable_file_names.push_back(answer_file_name); + irpc.input_file_name = "stdin"; + irpc.output_file_name = "stdout"; + irpc.error_file_name = result_path / "interactor_error.txt"; + irpc.program_name = data_path / "interactor"; + irpc.rest_args = {input_file_name, "/dev/stdin", answer_file_name}; + irpc.limits.real_time = rpc.limits.real_time = rpc.limits.time + irpc.limits.time + 1; + + runp::interaction::config ric; + ric.cmds.push_back(rpc.get_cmd()); + ric.cmds.push_back(irpc.get_cmd()); + + // from:fd, to:fd + ric.pipes.push_back(runp::interaction::pipe_config(2, 1, 1, 0, real_input_file_name)); + ric.pipes.push_back(runp::interaction::pipe_config(1, 1, 2, 0, real_output_file_name)); + + runp::interaction::run(ric); + + run_simple_interaction_result rires; + runp::result res = runp::result::from_file(rpc.result_file_name); + run_checker_result ires = run_checker_result::from_file( + irpc.error_file_name, runp::result::from_file(irpc.result_file_name)); + + if (res.type == runp::RS_AC && res.exit_code != 0) { + res.type = runp::RS_RE; + } + + if (ires.type == runp::RS_JGF) { + ires.info = "Interactor Judgment Failed"; + } + if (ires.type == runp::RS_TLE) { + ires.type = runp::RS_AC; + res.type = runp::RS_TLE; + } + + rires.res = res; + rires.ires = ires; + return rires; +} + +run_transformer_result transform_file(runp::config rpc, const string &in, const string &out) { + run_transformer_result res; + if (in == out) { + res.type = runp::RS_AC; + res.succeeded = true; + } else { + rpc.result_file_name = result_path / "run_trans_result.txt"; + rpc.input_file_name = in; + rpc.output_file_name = out; + rpc.error_file_name = result_path / "trans_error.txt"; + runp::result ret = runp::run(rpc); + res.type = ret.type; + res.succeeded = ret.type == runp::RS_AC && ret.exit_code == 0; + if (!res.succeeded) { + res.info = file_preview(rpc.error_file_name); + } + } + return res; +} + +/*======================== run End ==================== */ + +/*======================== compile ==================== */ + +run_compiler_result compile(const string &name) { + string lang = conf_str(name + "_language"); + runp::config rpc(main_path / "run" / "compile", + {"--custom", main_path / "run" / "runtime", "--lang", lang, name}); + rpc.limits = RL_COMPILER_DEFAULT; + return run_compiler(rpc); +} + +run_compiler_result compile_with_implementer(const string &name, + const string &implementer = "implementer") { + string lang = conf_str(name + "_language"); + if (conf_has(name + "_unit_name")) { + file_put_contents(work_path / (name + ".unit_name"), conf_str(name + "_unit_name")); + } + runp::config rpc(main_path / "run" / "compile", {"--custom", main_path / "run" / "runtime", + "--impl", implementer, "--lang", lang, name}); + rpc.limits = RL_COMPILER_DEFAULT; + return run_compiler(rpc); +} + +/*====================== compile End ================== */ + +/*====================== test ================== */ + +/* +struct TestPointConfig { + int submit_answer; + + int validate_input_before_test; + string input_file_name; + string output_file_name; + string answer_file_name; + + TestPointConfig() + : submit_answer(-1), validate_input_before_test(-1) { + } + + void auto_complete(int num) { + if (submit_answer == -1) { + submit_answer = conf_is("submit_answer", "on"); + } + if (validate_input_before_test == -1) { + validate_input_before_test = conf_is("validate_input_before_test", "on"); + } + if (input_file_name.empty()) { + input_file_name = data_path + "/" + conf_input_file_name(num); + } + if (output_file_name.empty()) { + output_file_name = work_path + "/" + conf_output_file_name(num); + } + if (answer_file_name.empty()) { + answer_file_name = data_path + "/" + conf_output_file_name(num); + } + } +}; +*/ + +/* + base class for testing one point + + for each member function, it returns false only if an internal error + occurs (due to OS, File System, or problem setter). + po.info stores the information that will be shown to the user the + first function returning false is responsible to set po.info and set + uoj_error with an error code. + + if an error occurs due to the user (e.g., the user's program RE), + po.info is set while the member function does not have to return false. + To check errors regardless its origin, use check(), which returns + false if po.info is non-empty and po.scr != 100. +*/ +class TestPoint { +public: + typedef int Author; + static const int AUTHOR_USERBIT = 1 << 8; + static const char TITLE_DISABLED[]; + static const char AUTHOR_NONAME[]; + + enum { AUTHOR_SETTER = 0, AUTHOR_STD }; + enum { AUTHOR_USER = AUTHOR_USERBIT, AUTHOR_USER2 }; + + int num = 0; + int validate_input_before_test = -1; + map fname; + map ftitle; + map fpreview_size; + map fauthor; + map author_name{{AUTHOR_SETTER, "Problem Setter"}, + {AUTHOR_STD, "Standard Program"}, + {AUTHOR_USER, AUTHOR_NONAME}}; + map program; + + PointInfo test(); + PointInfo hack_test(); + PointInfo custom_test(); + +private: + void config_sanity_check(); + +protected: + PointInfo po; + + fs::path get_fname(const string &fkey, const string &default_val = ""); + string get_basename(const string &key); + int get_fpreview_size(const string &fkey, int default_val = 100); + + runp::config get_program(const string &id); + runp::config search_program(const initializer_list &ids); + + void set_res_if_empty(const string &res); + void set_info_if_empty(const string &info); + void set_info_if_empty(const string &info, Author author); + bool add_info_block(const string &fkey); + + bool check(); + bool prepare_io(); + bool preprocess_input(const string &input); + bool postprocess_output(const string &output); + bool validate(const string &fkey); + bool encode(const string &input); + bool encrypt(const string &input); + bool decrypt(const string &output); + bool decode(const string &output); + bool grade(const string &input, const string &output, const string &answer); + bool run_custom(const string &id, const string &input, const string &output); + + virtual void complete_basic_config(); + virtual void complete_config(); + virtual void complete_hack_config(); + virtual void complete_custom_test_config(); + + virtual bool generate_answer(const string &input, const string &answer); + + virtual bool _test(); + virtual bool _hack_test(); + virtual bool _custom_test(); +}; + +const char TestPoint::TITLE_DISABLED[] = "disabled"; +const char TestPoint::AUTHOR_NONAME[] = "noname"; + +PointInfo TestPoint::test() { + try { + complete_basic_config(); + complete_config(); + config_sanity_check(); + _test(); + } catch (jgf_error &e) { + uoj_error(e.code); + set_info_if_empty(runp::rstype_str(runp::RS_JGF)); + } + if (po.info.empty()) { + uoj_error("PINFEM"); // po.info is empty + po.info = runp::rstype_str(runp::RS_JGF); + } + return po; +} + +PointInfo TestPoint::hack_test() { + try { + complete_basic_config(); + complete_hack_config(); + config_sanity_check(); + _hack_test(); + } catch (jgf_error &e) { + uoj_error(e.code); + set_info_if_empty(runp::rstype_str(runp::RS_JGF)); + } + if (po.info.empty()) { + uoj_error("PINFEM"); // po.info is empty + po.info = runp::rstype_str(runp::RS_JGF); + } + if (uoj_errcode.empty()) { + po.scr = po.scr != 100; + } + return po; +} + +PointInfo TestPoint::custom_test() { + try { + complete_basic_config(); + complete_custom_test_config(); + config_sanity_check(); + _custom_test(); + } catch (jgf_error &e) { + uoj_error(e.code); + set_info_if_empty(runp::rstype_str(runp::RS_JGF)); + } + if (po.info.empty()) { + uoj_error("PINFEM"); // po.info is empty + po.info = runp::rstype_str(runp::RS_JGF); + } + return po; +} + +void TestPoint::config_sanity_check() { + if (validate_input_before_test == -1) { + throw jgf_error("CFGERR1"); // config error + } + for (const auto &ft : ftitle) { + if (!fname.count(ft.first)) { + throw jgf_error("CFGERR2"); // config error + } + } + for (const auto &fp : fpreview_size) { + if (!fname.count(fp.first)) { + throw jgf_error("CFGERR3"); // config error + } + } + for (const auto &fn : fname) { + if (!fauthor.count(get_basename(fn.first))) { + throw jgf_error("CFGERR4"); // config error + } + if (!author_name.count(fauthor[get_basename(fn.first)])) { + throw jgf_error("CFGERR5"); // config error + } + } +} + +fs::path TestPoint::get_fname(const string &fkey, const string &default_val) { + return fname.count(fkey) ? fname[fkey] : fs::path(conf_str(fkey, default_val)); +} + +string TestPoint::get_basename(const string &key) { + size_t pos = key.find_last_of('.'); + if (pos == string::npos) { + return key; + } else { + return key.substr(0, pos); + } +} + +int TestPoint::get_fpreview_size(const string &fkey, int default_val) { + return fpreview_size.count(fkey) ? fpreview_size[fkey] : default_val; +} + +runp::config TestPoint::get_program(const string &id) { + try { + return program.at(id); + } catch (out_of_range &e) { + throw jgf_error("PROGNF1", "Program " + id + " Not Found"); // program not found + } +} +runp::config TestPoint::search_program(const initializer_list &ids) { + for (auto &id : ids) { + if (program.count(id)) { + return program[id]; + } + } + throw jgf_error("PROGNF2", + "Program " + list_to_string(ids) + " Not Found"); // program not found +} + +void TestPoint::set_res_if_empty(const string &res) { + if (po.res.empty()) { + po.res = res; + } +} + +void TestPoint::set_info_if_empty(const string &info) { + if (po.info.empty()) { + po.set_info(info); + } +} + +void TestPoint::set_info_if_empty(const string &info, Author author) { + if (po.info.empty()) { + if (!author_name.count(author)) { + po.set_info(info); + } else { + po.set_info(info + " (" + author_name[author] + ")"); + } + } +} + +bool TestPoint::add_info_block(const string &fkey) { + if (ftitle.count(fkey) && ftitle[fkey] != "disabled") { + po.li.push_back( + InfoBlock::from_file_with_size(ftitle[fkey], fname[fkey], get_fpreview_size(fkey))); + } + return true; +} + +bool TestPoint::check() { + return po.info.empty() || po.scr == 100; +} + +bool TestPoint::prepare_io() { + try { + fs::remove_all(work_path / "io"); + fs::create_directories(work_path / "io"); + return true; + } catch (exception &e) { + throw jgf_error("PPIOFAL"); // prepare_io failed + } +} + +bool TestPoint::preprocess_input(const string &input) { + return validate(input) && check() && encode(input) && check() && encrypt(input) && check(); +} + +bool TestPoint::postprocess_output(const string &output) { + return decrypt(output) && check() && decode(output) && check(); +} + +bool TestPoint::validate(const string &fkey) { + if (!validate_input_before_test) { + return true; + } + + run_validator_result ret = run_validator(get_program("val"), fname[fkey]); + if (ret.type != runp::RS_AC) { + throw jgf_error("VALFAL", "Validator " + runp::rstype_str(ret.type)); // validator failed + } else if (!ret.succeeded) { + set_res_if_empty(ret.info); + set_info_if_empty("Invalid Input", fauthor[fkey]); + if (!(fauthor[fkey] & AUTHOR_USERBIT)) { + throw jgf_error("INVINV"); // invalid input (found by val) + } + } + return true; +} + +bool TestPoint::encode(const string &input) { + run_transformer_result ret = transform_file(search_program({input + "encoder", "encoder"}), + fname[input], fname[input + ".plain"]); + add_info_block(input + ".plain"); + if (ret.type != runp::RS_AC) { + throw jgf_error("ECOFAL", "Encoder " + runp::rstype_str(ret.type)); // encoder failed + } else if (!ret.succeeded) { + set_res_if_empty(ret.info); + set_info_if_empty("Invalid Input", fauthor[input]); + if (!(fauthor[input] & AUTHOR_USERBIT)) { + throw jgf_error("INVINO"); // invalid input (found by encode) + } + } + return true; +} + +bool TestPoint::encrypt(const string &input) { + run_transformer_result ret = transform_file(search_program({input + "encrypter", "encrypter"}), + fname[input + ".plain"], fname[input + ".raw"]); + add_info_block(input + ".raw"); + if (ret.type != runp::RS_AC) { + throw jgf_error("ECRFAL", "Encrypter " + runp::rstype_str(ret.type)); // encrypter failed + } else if (!ret.succeeded) { + set_res_if_empty(ret.info); + set_info_if_empty("Invalid Input", fauthor[input]); + if (!(fauthor[input] & AUTHOR_USERBIT)) { + throw jgf_error("INVINC"); // invalid input (found by encrypt) + } + } + return true; +} + +bool TestPoint::decrypt(const string &output) { + run_transformer_result ret = transform_file(search_program({output + "decrypter", "decrypter"}), + fname[output + ".raw"], fname[output + ".plain"]); + add_info_block(output + ".plain"); + if (ret.type != runp::RS_AC) { + throw jgf_error("DCRFAL", "Decrypter " + runp::rstype_str(ret.type)); // decrypter failed + } else if (!ret.succeeded) { + set_res_if_empty(ret.info); + if (!(fauthor[output] & AUTHOR_USERBIT)) { + set_info_if_empty("Invalid Output", fauthor[output]); + throw jgf_error("INTWAC"); // internal WA (found by encrypt) + } + set_info_if_empty("Wrong Answer", fauthor[output]); + } + return true; +} + +bool TestPoint::decode(const string &output) { + run_transformer_result ret = transform_file(search_program({output + "decoder", "decoder"}), + fname[output + ".plain"], fname[output]); + add_info_block(output); + if (ret.type != runp::RS_AC) { + throw jgf_error("DCOFAL", "Decrypter " + runp::rstype_str(ret.type)); // decoder failed + } else if (!ret.succeeded) { + set_res_if_empty(ret.info); + if (!(fauthor[output] & AUTHOR_USERBIT)) { + set_info_if_empty("Invalid Output", fauthor[output]); + throw jgf_error("INTWAO"); // internal WA (found by encode) + } + set_info_if_empty("Wrong Answer", fauthor[output]); + } + return true; +} + +bool TestPoint::generate_answer(const string &input, const string &answer) { + runp::result ret = + run_submission_program(get_program("std"), fname["input.raw"], fname["answer.raw"]); + if (ret.type != runp::RS_AC) { + set_info_if_empty("Standard Program " + runp::rstype_str(ret.type)); + throw jgf_error("STDFAL"); // std failed + } + return true; +} + +bool TestPoint::grade(const string &input, const string &output, const string &answer) { + run_checker_result ret = + run_checker(get_program("chk"), fname[input], fname[output], fname[answer]); + if (ret.type != runp::RS_AC) { + uoj_error("CHKFAL"); // checker failed + set_info_if_empty("Checker " + runp::rstype_str(ret.type)); + return false; + } else { + po.scr = ret.scr; + set_res_if_empty(ret.info); + set_info_if_empty("default"); + } + return true; +} + +bool TestPoint::run_custom(const string &id, const string &input, const string &output) { + if (!program.count(id)) { + throw jgf_error("PRGNF"); // program not found + } + + runp::result ret = + run_submission_program(get_program(id), fname[input + ".raw"], fname[output + ".raw"]); + set_res_if_empty(ret.extra); + if (ret.type != runp::RS_AC) { + set_info_if_empty(runp::rstype_str(ret.type)); + if (ret.type == runp::RS_JGF) { + throw jgf_error("RPUSRJGF"); // run_program(user's program) JGF + } + } else { + set_info_if_empty("Success"); + po.scr = 100; + po.ust = ret.ust; + po.usm = ret.usm; + } + return true; +} + +void TestPoint::complete_basic_config() { + po.num = num; + if (validate_input_before_test == -1) { + validate_input_before_test = conf_is("validate_input_before_test", "on"); + } +} + +void TestPoint::complete_config() { + map_add(fauthor, + {{"input", AUTHOR_SETTER}, {"output", AUTHOR_USER}, {"answer", AUTHOR_SETTER}}); + + map_add(fname, {{"input", data_path / conf_input_file_name(num)}, + {"input.plain", work_path / "io" / "plain_input.txt"}, + {"input.raw", work_path / "io" / "raw_input.txt"}, + {"output.raw", work_path / "io" / "raw_output.txt"}, + {"output.plain", work_path / "io" / "plain_output.txt"}, + {"output", work_path / "io" / "output.txt"}, + {"answer", data_path / conf_output_file_name(num)}}); + + if (get_fname("input_encoder", "cp") == "cp") { + map_add(ftitle, {{"input", "input"}, {"output.plain", "output"}}); + } else { + map_add(ftitle, {{"input", "data_for_input_generation"}, + {"input.plain", "input"}, + {"output.plain", "output"}}); + } +} + +void TestPoint::complete_hack_config() { + map_add(fauthor, {{"input", AUTHOR_USER}, {"output", AUTHOR_USER}, {"answer", AUTHOR_STD}}); + + map_add(fname, {{"input", work_path / "hack_input.txt"}, + {"input.plain", work_path / "io" / "plain_input.txt"}, + {"input.raw", work_path / "io" / "raw_input.txt"}, + {"output.raw", work_path / "io" / "raw_output.txt"}, + {"output.plain", work_path / "io" / "plain_output.txt"}, + {"output", work_path / "pro_output.txt"}, + {"answer.raw", work_path / "io" / "raw_std_output.txt"}, + {"answer.plain", work_path / "io" / "plain_std_output.txt"}, + {"answer", work_path / "std_output.txt"}}); + + if (get_fname("input_encoder", "cp") == "cp") { + map_add(ftitle, {{"input", "input"}, {"output.plain", "output"}}); + } else { + map_add(ftitle, {{"input", "data_for_input_generation"}, + {"input.plain", "input"}, + {"output.plain", "output"}}); + } +} + +void TestPoint::complete_custom_test_config() { + map_add(fauthor, {{"input", AUTHOR_USER}, {"output", AUTHOR_USER}}); + + map_add(fname, {{"input.plain", work_path / "input.txt"}, + {"input.raw", work_path / "io" / "raw_input.txt"}, + {"output.raw", work_path / "io" / "raw_output.txt"}, + {"output.plain", work_path / "io" / "plain_output.txt"}}); + + if (get_fname("input_encoder", "cp") == "cp") { + map_add(ftitle, {{"input", "input"}, {"output.plain", "output"}}); + } else { + map_add(ftitle, {{"input", "data_for_input_generation"}, + {"input.plain", "input"}, + {"output.plain", "output"}}); + } + + map_add(fpreview_size, {{"output.plain", 2048}}); +} + +bool TestPoint::_test() { + uoj_error("TPTSTCAL"); // TestPoint::_test() is called + po.set_info(runp::rstype_str(runp::RS_JGF)); + return false; +} +bool TestPoint::_hack_test() { + uoj_error("TPHKTCAL"); // TestPoint::_hack_test() is called + po.set_info(runp::rstype_str(runp::RS_JGF)); + return false; +} +bool TestPoint::_custom_test() { + return prepare_io() && add_info_block("input") && encrypt("input") + && run_custom("answer", "input", "output") && decrypt("output") && check(); +} + +class SubmitAnswerTestPoint : public TestPoint { +protected: + virtual void complete_config() override { + map_add(fname, {{"input", data_path / conf_input_file_name(num)}, + {"input.plain", data_path / conf_input_file_name(num)}, + {"output.plain", work_path / conf_output_file_name(num)}, + {"output", work_path / conf_output_file_name(num)}, + {"answer", data_path / conf_output_file_name(num)}}); + TestPoint::complete_config(); + } + + virtual bool _test() override { + return prepare_io() && add_info_block("input") && encode("input") && check() + && decode("output") && check() && grade("input", "output", "answer") && check(); + } +}; + +class SingleProgramTestPoint : public TestPoint { +protected: + virtual bool run() { + if (!program.count("answer")) { + uoj_error("PRGNF"); // program not found + set_info_if_empty(runp::rstype_str(runp::RS_JGF)); + return false; + } + + runp::result ret = + run_submission_program(get_program("answer"), fname["input.raw"], fname["output.raw"]); + if (ret.type != runp::RS_AC) { + set_info_if_empty(runp::rstype_str(ret.type)); + set_res_if_empty(ret.extra); + if (ret.type == runp::RS_JGF) { + uoj_error("RPUSRJGF"); // run_program(user's program) JGF + return false; + } + } else { + po.ust = ret.ust; + po.usm = ret.usm; + } + return true; + } + + virtual bool _test() override { + return prepare_io() && add_info_block("input") && preprocess_input("input") && run() + && postprocess_output("output") && grade("input", "output", "answer") && check(); + } + + virtual bool _hack_test() override { + return prepare_io() && add_info_block("input") && preprocess_input("input") + && generate_answer("input", "answer") && postprocess_output("answer") && run() + && postprocess_output("output") && grade("input", "output", "answer") && check(); + } +}; + +class SimpleInteractionTestPoint : public SingleProgramTestPoint { +public: + enum { AUTHOR_INTERACTOR = 1 << 4 }; + +protected: + virtual void complete_config() override { + map_add(fauthor, {{"program_input", AUTHOR_INTERACTOR}}); + map_add(author_name, {{AUTHOR_INTERACTOR, "Interactor"}}); + map_add(fname, {{"program_input.raw", work_path / "io" / "raw_program_input.txt"}, + {"program_input.plain", work_path / "io" / "plain_program_input.txt"}}); + + if (get_fname("input_encoder", "cp") == "cp") { + map_add(ftitle, {{"input", "input_to_interactor"}, + {"input.plain", TITLE_DISABLED}, + {"program_input.plain", "input"}}); + } + SingleProgramTestPoint::complete_config(); + } + + virtual bool run() { + if (!program.count("answer") || !program.count("interactor")) { + uoj_error("PRGNF"); // program not found + set_info_if_empty(runp::rstype_str(runp::RS_JGF)); + return false; + } + + // conf_run_limit(num, RL_DEFAULT), + // conf_run_limit("interactor", num, RL_INTERACTOR_DEFAULT), + run_simple_interaction_result rires = run_simple_interaction( + fname["input.raw"], fname["answer"], fname["program_input.raw"], fname["output.raw"], + get_program("answer"), get_program("interactor")); + + if (rires.ires.type != runp::RS_AC) { + uoj_error("INTFAL"); // interactor failed + set_info_if_empty("Interactor " + runp::rstype_str(rires.ires.type)); + return false; + } else if (rires.res.type != runp::RS_AC) { + set_info_if_empty(runp::rstype_str(rires.res.type)); + if (rires.res.type == runp::RS_JGF) { + uoj_error("RPUSRJGF"); // run_program(user's program) JGF + return false; + } + } else { + po.scr = rires.ires.scr; + po.ust = rires.res.ust; + po.usm = rires.res.usm; + po.res = rires.ires.info; + po.set_info("default"); + } + return true; + } + + virtual bool run_std() { + // ??? + return true; + } + + virtual bool _test() override { + return prepare_io() && add_info_block("input") && preprocess_input("input") && run() + && decrypt("program_input") && decrypt("output") && check(); + } + + virtual bool _hack_test() override { + return prepare_io() && add_info_block("input") + && preprocess_input("input") + // && run_std() + && run() && decrypt("program_input") && decrypt("output") && check(); + } +}; + +/* +class TwoRoundTestPoint : public TestPoint { +public: + string alice_name = "alice"; + string bob_name = "bob"; + +protected: + virtual void complete_config() override { + TestPoint::complete_config(); + } + + virtual bool transport() { + return true; + } + + virtual bool run_alice() { + return true; + } + + virtual bool run_bob() { + return true; + } + + virtual bool _test() override { + return prepare_io() && prepare_input("input") + && run_alice() && decrypt("alice_output") && check() + && decode("alice_output") && check() + && transport() + && prepare_input("bob_input") + && run_bob() && decrypt("bob_output") && check() + && decode("bob_output") && check() + && grade() && check(); + } +};*/ + +/* +PointInfo test_point(const string &name, const int &num, TestPointConfig tpc = TestPointConfig()) { + tpc.auto_complete(num); + + if (tpc.validate_input_before_test) { + RunValidatorResult val_ret = run_validator( + tpc.input_file_name, + conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), + conf_str("validator")); + if (val_ret.type != RS_AC) { + return PointInfo(num, 0, -1, -1, + "Validator " + runp::rstype_str(val_ret.type), + file_preview(tpc.input_file_name), "", + ""); + } else if (!val_ret.succeeded) { + return PointInfo(num, 0, -1, -1, + "Invalid Input", + file_preview(tpc.input_file_name), "", + val_ret.info); + } + } + + if (!conf_is("interaction_mode", "on")) { + runp::result pro_ret; + if (!tpc.submit_answer) { + pro_ret = run_submission_program( + tpc.input_file_name.c_str(), + tpc.output_file_name.c_str(), + conf_run_limit(num, RL_DEFAULT), + name); + if (conf_has("token")) { + file_hide_token(tpc.output_file_name, conf_str("token", "")); + } + if (pro_ret.type != RS_AC) { + return PointInfo(num, 0, -1, -1, + runp::rstype_str(pro_ret.type), + file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), + ""); + } + } else { + pro_ret.type = RS_AC; + pro_ret.ust = -1; + pro_ret.usm = -1; + pro_ret.exit_code = 0; + } + + RunCheckerResult chk_ret = run_checker( + conf_run_limit("checker", num, RL_CHECKER_DEFAULT), + conf_str("checker"), + tpc.input_file_name, + tpc.output_file_name, + tpc.answer_file_name); + if (chk_ret.type != RS_AC) { + return PointInfo(num, 0, -1, -1, + "Checker " + runp::rstype_str(chk_ret.type), + file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), + ""); + } + + return PointInfo(num, chk_ret.scr, pro_ret.ust, pro_ret.usm, + "default", + file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), + chk_ret.info); + } else { + string real_output_file_name = tpc.output_file_name + ".real_input.txt"; + string real_input_file_name = tpc.output_file_name + ".real_output.txt"; + RunSimpleInteractionResult rires = run_simple_interaction( + tpc.input_file_name, + tpc.answer_file_name, + real_input_file_name, + real_output_file_name, + conf_run_limit(num, RL_DEFAULT), + conf_run_limit("interactor", num, RL_INTERACTOR_DEFAULT), + name); + + if (rires.ires.type != RS_AC) { + return PointInfo(num, 0, -1, -1, + "Interactor " + runp::rstype_str(rires.ires.type), + file_preview(real_input_file_name), file_preview(real_output_file_name), + ""); + } + if (rires.res.type != RS_AC) { + return PointInfo(num, 0, -1, -1, + runp::rstype_str(rires.res.type), + file_preview(real_input_file_name), file_preview(real_output_file_name), + ""); + } + + return PointInfo(num, rires.ires.scr, rires.res.ust, rires.res.usm, + "default", + file_preview(real_input_file_name), file_preview(real_output_file_name), + rires.ires.info); + } +}*/ + +/* +PointInfo test_hack_point(const string &name, TestPointConfig tpc) { + tpc.submit_answer = false; + tpc.validate_input_before_test = false; + tpc.auto_complete(0); + RunValidatorResult val_ret = run_validator( + tpc.input_file_name, + conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), + conf_str("validator")); + if (val_ret.type != RS_AC) { + return PointInfo(0, 0, -1, -1, + "Validator " + runp::rstype_str(val_ret.type), + file_preview(tpc.input_file_name), "", + ""); + } else if (!val_ret.succeeded) { + return PointInfo(0, 0, -1, -1, + "Invalid Input", + file_preview(tpc.input_file_name), "", + val_ret.info); + } + + RunLimit default_std_run_limit = conf_run_limit(0, RL_DEFAULT); + + prepare_run_standard_program(); + if (!conf_is("interaction_mode", "on")) { + runp::config rpc; + rpc.result_file_name = result_path + "/run_standard_program.txt"; + runp::result std_ret = run_submission_program( + tpc.input_file_name, + tpc.answer_file_name, + conf_run_limit("standard", 0, default_std_run_limit), + "std", + rpc); + if (std_ret.type != RS_AC) { + return PointInfo(0, 0, -1, -1, + "Standard Program " + runp::rstype_str(std_ret.type), + file_preview(tpc.input_file_name), "", + ""); + } + if (conf_has("token")) { + file_hide_token(tpc.answer_file_name, conf_str("token", "")); + } + } else { + runp::config rpc; + rpc.result_file_name = result_path + "/run_standard_program.txt"; + string real_output_file_name = tpc.answer_file_name; + string real_input_file_name = tpc.output_file_name + ".real_output.txt"; + RunSimpleInteractionResult rires = run_simple_interaction( + tpc.input_file_name, + tpc.answer_file_name, + real_input_file_name, + real_output_file_name, + conf_run_limit("standard", 0, default_std_run_limit), + conf_run_limit("interactor", 0, RL_INTERACTOR_DEFAULT), + "std", + rpc); + + if (rires.ires.type != RS_AC) { + return PointInfo(0, 0, -1, -1, + "Interactor " + runp::rstype_str(rires.ires.type) + " (Standard Program)", + file_preview(real_input_file_name), "", + ""); + } + if (rires.res.type != RS_AC) { + return PointInfo(0, 0, -1, -1, + "Standard Program " + runp::rstype_str(rires.res.type), + file_preview(real_input_file_name), "", + ""); + } + } + + PointInfo po = test_point(name, 0, tpc); + po.scr = po.scr != 100; + return po; +}*/ + +/* +CustomTestInfo ordinary_custom_test(const string &name) { + RunLimit lim = conf_run_limit(0, RL_DEFAULT); + lim.time += 2; + + string input_file_name = work_path + "/input.txt"; + string output_file_name = work_path + "/output.txt"; + + esult pro_ret = run_submission_program( + input_file_name, + output_file_name, + lim, + name); + if (conf_has("token")) { + file_hide_token(output_file_name, conf_str("token", "")); + } + string info; + if (pro_ret.type == RS_AC) { + info = "Success"; + } else { + info = runp::rstype_str(pro_ret.type); + } + string exp; + if (pro_ret.type == RS_TLE) { + exp = "

[time limit: " + vtos(lim.time) + "s]

"; + } + return CustomTestInfo(pro_ret.ust, pro_ret.usm, + info, exp, file_preview(output_file_name, 2048)); +} +*/ + +int scale_score(int scr100, int full) { + return scr100 * full / 100; +} + +/*====================== test End ================== */ + +/*====================== judger ================== */ + +struct SubtaskInfo { + bool passed; + int score; + + SubtaskInfo() {} + SubtaskInfo(const bool &_p, const int &_s) : passed(_p), score(_s) {} +}; + +class Judger { +protected: + map program; + + bool add_program(const string &id, const runp::config &candidate) { + if (program.count(id)) { + return false; + } + if (candidate.type == "internal") { + program[id] = candidate; + return true; + } + if (fs::exists(candidate.program_name)) { + program[id] = candidate; + return true; + } + return false; + } + + bool compile_and_add_program(const string &name) { + run_compiler_result c_ret = !conf_is("with_implementer", "on") + ? compile("answer") + : compile_with_implementer("answer"); + if (!c_ret.succeeded) { + end_judge_compile_error(c_ret); + } + + runp::config rpc("./" + name); + rpc.set_type(runp::get_type_from_lang(conf_str(name + "_language"))); + add_program(name, rpc); + return true; + } + + void add_readable_to_program(runp::config &rpc) { + int p = 1; + while (true) { + string fname = conf_str("readable", p, ""); + if (fname.empty()) { + break; + } + if (fname[0] != '/') { + fname = work_path / fname; + } + rpc.readable_file_names.push_back(fname); + p++; + } + } + + void add_writable_to_program(runp::config &rpc) { + int p = 1; + while (true) { + string fname = conf_str("writable", p, ""); + if (fname.empty()) { + break; + } + if (fname[0] != '/') { + fname = work_path / fname; + } + rpc.writable_file_names.push_back(fname); + p++; + } + } + + virtual void configure_programs_for_point(int num) { + if (program.count("std")) { + program["std"].limits = + conf_run_limit("standard", num, conf_run_limit(num, RL_DEFAULT)); + } + if (program.count("chk")) { + program["chk"].limits = conf_run_limit("checker", num, RL_CHECKER_DEFAULT); + } + if (program.count("val")) { + program["val"].limits = conf_run_limit("validator", num, RL_VALIDATOR_DEFAULT); + } + + for (auto &name : {"encoder", "encrypter", "decrypter", "decoder"}) { + if (program.count(name)) { + program[name].limits = conf_run_limit(name, num, RL_TRANSFORMER_DEFAULT); + } + } + } + + virtual void prepare() { + if (conf_has("use_builtin_checker")) { + add_program("chk", runp::config(main_path / "builtin" / "checker" + / conf_str("use_builtin_checker"))); + } + + // search for chk, val, std, encoder, decoder, etc. + for (auto &p : fs::directory_iterator(data_path)) { + if ((p.status().permissions() & fs::perms::others_exec) != fs::perms::none) { + add_program(p.path().filename(), runp::config(p.path())); + } + } + + add_program("encoder", runp::config("cp").set_type("internal")); + add_program("encrypter", runp::config("cp").set_type("internal")); + if (conf_has("token")) { + add_program("decrypter", runp::config("hide_token").set_type("internal")); + } else { + add_program("decrypter", runp::config("cp").set_type("internal")); + } + add_program("decoder", runp::config("cp").set_type("internal")); + + for (auto &kv : program) { + add_readable_to_program(kv.second); + add_writable_to_program(kv.second); + } + } + + virtual void prepare_ordinary_test() {} + + virtual void prepare_sample_test() {} + + virtual void prepare_hack_test() {} + + virtual void prepare_custom_test() {} + + virtual PointInfo test_point(int num) { + end_judge_judgment_failed(runp::rstype_str(runp::RS_JGF)); + } + + virtual PointInfo test_sample_point(int num) { + end_judge_judgment_failed("Sample test is not supported in this problem."); + } + + virtual PointInfo test_hack_point() { + end_judge_judgment_failed("Hack is not supported in this problem."); + } + + virtual PointInfo test_custom_point() { + end_judge_judgment_failed("Custom test is not supported in this problem."); + } + +public: + virtual int n_tests() { + return conf_int("n_tests", 10); + } + + virtual int n_ex_tests() { + return conf_int("n_ex_tests", 0); + } + + virtual int n_sample_tests() { + return conf_int("n_sample_tests", 0); + } + + virtual int sample_test_point_score(int num) { + return 100 / this->n_sample_tests(); + } + + virtual bool is_hack_enabled() { + return true; + } + + virtual bool is_custom_test_enabled() { + return true; + } + + virtual void ordinary_test() { + int n = conf_int("n_tests", 10); + int m = this->n_ex_tests(); + int nT = conf_int("n_subtasks", 0); + + this->prepare(); + this->prepare_ordinary_test(); + + bool passed = true; + if (nT == 0) { // OI + for (int i = 1; i <= n; i++) { + report_judge_status_f("Judging Test #%d", i); + PointInfo po = this->test_point(i); + if (po.scr != 100) { + passed = false; + } + po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n)); + add_point_info(po); + } + } else if (nT == 1 && conf_str("subtask_type", 1, "packed") == "packed") { // ACM + for (int i = 1; i <= n; i++) { + report_judge_status_f("Judging Test #%d", i); + PointInfo po = this->test_point(i); + if (po.scr != 100) { + passed = false; + po.scr = i == 1 ? 0 : -100; + add_point_info(po); + break; + } else { + po.scr = i == 1 ? 100 : 0; + add_point_info(po); + } + } + } else { // subtask + map subtasks; + map minScore; + for (int t = 1; t <= nT; t++) { + string subtaskType = conf_str("subtask_type", t, "packed"); + int startI = conf_int("subtask_end", t - 1, 0) + 1; + int endI = conf_int("subtask_end", t, 0); + + vector points; + minScore[t] = 100; + + vector dependences; + if (conf_str("subtask_dependence", t, "none") == "many") { + string cur = "subtask_dependence_" + to_string(t); + int p = 1; + while (conf_int(cur, p, 0) != 0) { + dependences.push_back(conf_int(cur, p, 0)); + p++; + } + } else if (conf_int("subtask_dependence", t, 0) != 0) { + dependences.push_back(conf_int("subtask_dependence", t, 0)); + } + bool skipped = false; + for (vector::iterator it = dependences.begin(); it != dependences.end(); + it++) { + if (subtaskType == "packed") { + if (!subtasks[*it].passed) { + skipped = true; + break; + } + } else if (subtaskType == "min") { + minScore[t] = min(minScore[t], minScore[*it]); + if (minScore[t] == 0) { + skipped = true; + break; + } + } + } + if (skipped) { + add_subtask_info(t, 0, "Skipped", points); + continue; + } + + int tfull = conf_int("subtask_score", t, 100 / nT); + int tscore = scale_score(minScore[t], tfull); + string info = "Accepted"; + for (int i = startI; i <= endI; i++) { + report_judge_status_f("Judging Test #%d of Subtask #%d", i, t); + PointInfo po = this->test_point(i); + if (subtaskType == "packed") { + if (po.scr != 100) { + passed = false; + po.scr = i == startI ? 0 : -tfull; + tscore = 0; + points.push_back(po); + info = po.info; + break; + } else { + po.scr = i == startI ? tfull : 0; + tscore = tfull; + points.push_back(po); + } + } else if (subtaskType == "min") { + minScore[t] = min(minScore[t], po.scr); + if (po.scr != 100) { + passed = false; + } + po.scr = scale_score(po.scr, tfull); + if (po.scr <= tscore) { + tscore = po.scr; + points.push_back(po); + info = po.info; + if (tscore == 0) { + break; + } + } else { + points.push_back(po); + } + } + } + + subtasks[t] = SubtaskInfo(info == "Accepted", tscore); + + add_subtask_info(t, tscore, info, points); + } + } + if (!passed) { + end_judge_ok(); + } + + tot_score = 100; + for (int i = 1; i <= m; i++) { + report_judge_status_f("Judging Extra Test #%d", i); + PointInfo po = this->test_point(-i); + if (po.scr != 100) { + po.num = -1; + po.info = "Extra Test Failed : " + po.info + " on " + to_string(i); + po.scr = -3; + add_point_info(po); + end_judge_ok(); + } + } + if (m != 0) { + add_point_info(PointInfo::extra_test_passed()); + } + end_judge_ok(); + } + + virtual void hack_test() { + if (!this->is_hack_enabled()) { + end_judge_judgment_failed("Hack is not supported in this problem."); + } else { + this->prepare(); + this->prepare_hack_test(); + add_point_info(this->test_hack_point()); + end_judge_ok(); + } + } + + virtual void sample_test() { + this->prepare(); + this->prepare_sample_test(); + int n = this->n_sample_tests(); + bool passed = true; + for (int i = 1; i <= n; i++) { + report_judge_status_f("Judging Sample Test #%d", i); + PointInfo po = this->test_sample_point(i); + if (po.scr != 100) { + passed = false; + } + po.scr = scale_score(po.scr, this->sample_test_point_score(i)); + add_point_info(po); + } + if (passed) { + tot_score = 100; + } + end_judge_ok(); + } + + virtual void custom_test() { + if (!is_custom_test_enabled()) { + end_judge_judgment_failed("Custom test is not supported in this problem."); + } else { + this->prepare(); + this->prepare_custom_test(); + + report_judge_status_f("Judging"); + add_custom_test_info(this->test_custom_point()); + + end_judge_ok(); + } + } + + virtual void judge() { + if (conf_is("test_new_hack_only", "on")) { + this->hack_test(); + } else if (conf_is("test_sample_only", "on")) { + this->sample_test(); + } else if (conf_is("custom_test", "on")) { + this->custom_test(); + } else { + this->ordinary_test(); + } + } +}; + +template +class OrdinaryJudger : public Judger { +protected: + virtual void configure_programs_for_point(int num) { + program["answer"].limits = conf_run_limit(num, RL_DEFAULT); + Judger::configure_programs_for_point(num); + } + + virtual void prepare() override { + report_judge_status_f("Compiling"); + compile_and_add_program("answer"); + Judger::prepare(); + } + + virtual PointInfo test_point(int num) override { + TP tp; + tp.num = num; + Judger::configure_programs_for_point(num); + tp.program = program; + return tp.test(); + } + + virtual PointInfo test_sample_point(int num) override { + PointInfo po = this->test_point(-num); + po.num = num; + return po; + } + + virtual PointInfo test_hack_point() override { + TP tp; + tp.num = 0; + tp.validate_input_before_test = true; + Judger::configure_programs_for_point(0); + tp.program = program; + return tp.hack_test(); + } + + virtual PointInfo test_custom_point() override { + TP tp; + tp.num = 0; + Judger::configure_programs_for_point(0); + tp.program = program; + tp.program["answer"].limits.time += 2; + return tp.custom_test(); + } +}; + +class SubmitAnswerJudger : public Judger { +protected: + virtual PointInfo test_point(int num) override { + SubmitAnswerTestPoint tp; + tp.num = num; + Judger::configure_programs_for_point(num); + tp.program = program; + return tp.test(); + } + + virtual PointInfo test_sample_point(int num) override { + if (conf_is("check_existence_only_in_sample_test", "on")) { + SubmitAnswerTestPoint tp; + tp.num = num; + tp.program = program; + tp.program["chk"] = runp::config("nonempty").set_type("internal"); + Judger::configure_programs_for_point(num); + return tp.test(); + } else { + PointInfo po = this->test_point(num); + if (po.scr != 0) { + po.info = "Accepted"; + po.scr = 100; + } + po.res = "no comment"; + return po; + } + } + +public: + virtual int n_ex_tests() override { + return 0; + } + + virtual int n_sample_tests() override { + return this->n_tests(); + } + + virtual int sample_test_point_score(int num) override { + return conf_int("point_score", num, 100 / this->n_sample_tests()); + } + + virtual bool is_hack_enabled() override { + return false; + } + + virtual bool is_custom_test_enabled() override { + return false; + } +}; + +/*====================== judger end ================== */ + +/*======================= conf init =================== */ + +void main_judger_init(int argc, char **argv) { + try { + main_path = fs::read_symlink("/proc/self/exe").parent_path(); + work_path = fs::current_path() / "work"; + result_path = fs::current_path() / "result"; + load_config(work_path / "submission.conf"); + data_path = main_path / "data" / conf_str("problem_id"); + load_config(data_path / "problem.conf"); + + if (fs::is_directory(data_path / "require")) { + fs::copy(data_path / "require", work_path, + fs::copy_options::update_existing | fs::copy_options::recursive); + } + + if (conf_is("use_builtin_judger", "on")) { + conf_add("judger", main_path / "builtin" / "judger" / "judger"); + } else { + conf_add("judger", data_path / "judger"); + } + + runp::run_path = main_path / "run"; + + } catch (exception &e) { + cerr << e.what() << endl; + exit(1); + } +} +void judger_init(int argc, char **argv) { + if (argc != 5) { + cerr << "judger: argc != 5" << endl; + exit(1); + } + main_path = argv[1]; + work_path = argv[2]; + result_path = argv[3]; + data_path = argv[4]; + load_config(work_path / "submission.conf"); + load_config(data_path / "problem.conf"); + runp::run_path = main_path / "run"; + + fs::current_path(work_path); +} + +/*===================== conf init End ================= */ + +/*===================== default judger ================ */ + +int default_judger_main(int argc, char **argv) { + judger_init(argc, argv); + + Judger *judger; + if (conf_is("submit_answer", "on")) { + judger = new SubmitAnswerJudger(); + } else if (conf_is("interaction_mode", "on")) { + judger = new OrdinaryJudger(); + } else { + judger = new OrdinaryJudger(); + } + judger->judge(); + + return -1; // error +} + +/*===================== default judger End ============ */ diff --git a/judger/uoj_judger/include/uoj_run.h b/judger/uoj_judger/include/uoj_run.h new file mode 100644 index 000000000..89f742d5a --- /dev/null +++ b/judger/uoj_judger/include/uoj_run.h @@ -0,0 +1,470 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UOJ_GCC "/usr/bin/gcc" +#define UOJ_GPLUSPLUS "/usr/bin/g++" +#define UOJ_PYTHON2 "/usr/local/bin/python2.7" +#define UOJ_PYTHON3 "/usr/bin/python3.12" +#define UOJ_PYTHON3_VERSION "3.12" +#define UOJ_FPC "/usr/bin/fpc" +#define UOJ_FPC_VERSION "3.2.2" +#define UOJ_JDK "/usr/lib/jvm/java-21-openjdk-amd64" +#define UOJ_JAVA_VERSION "21" + +std::string escapeshellarg(int arg) { + return std::to_string(arg); +} +std::string escapeshellarg(double arg) { + std::ostringstream sout; + sout << std::setprecision(15) << arg; + return sout.str(); +} +std::string escapeshellarg(const std::string &arg) { + std::string res = "'"; + for (char c : arg) { + if (c == '\'') { + res += "'\\''"; + } else { + res += c; + } + } + res += "'"; + return res; +} + +template +std::ostream &spaced_out(std::ostream &out, const T &arg) { + return out << arg; +} + +template +std::ostream &spaced_out(std::ostream &out, const T &arg, const Args &...rest) { + return spaced_out(out << arg << " ", rest...); +} + +template +std::ostream &add_spaced_out(std::ostream &out, const T &arg) { + return out << " " << arg; +} + +template +std::ostream &add_spaced_out(std::ostream &out, const T &arg, const Args &...rest) { + return spaced_out(out << " " << arg, rest...); +} + +template +int execute(const Args &...args) { + std::ostringstream sout; + spaced_out(sout, args...); +#ifdef UOJ_SHOW_EVERY_CMD + std::cerr << sout.str() << std::endl; +#endif + int status = system(sout.str().c_str()); + if (status == -1 || !WIFEXITED(status)) { + return -1; + } + return WEXITSTATUS(status); +} + +int executef(const char *fmt, ...) { + constexpr int L = 1 << 10; + char cmd[L]; + va_list ap; + va_start(ap, fmt); + int res = vsnprintf(cmd, L, fmt, ap); + if (res < 0 || res >= L) { + return -1; + } + res = execute(cmd); + va_end(ap); + return res; +} + +class cannot_determine_class_name_error : std::invalid_argument { +public: + explicit cannot_determine_class_name_error() : + std::invalid_argument("cannot determine the class name!") {} +}; + +std::string get_class_name_from_file(const std::string &fname) { + std::ifstream fin(fname); + if (!fin) { + throw cannot_determine_class_name_error(); + } + std::string class_name; + if (!(fin >> class_name)) { + throw cannot_determine_class_name_error(); + } + if (class_name.length() > 100) { + throw cannot_determine_class_name_error(); + } + for (char &c : class_name) { + if (!isalnum(c) && c != '_') { + throw cannot_determine_class_name_error(); + } + } + return class_name; +} + +bool put_class_name_to_file(const std::string &fname, const std::string &class_name) { + std::ofstream fout(fname); + if (!fout) { + return false; + } + if (!(fout << class_name << std::endl)) { + return false; + } + return true; +} + +std::map lang_upgrade_map = { + {"C", "C17"}, + {"C++", "C++17"}, +}; + +std::string upgraded_lang(const std::string &lang) { + return lang_upgrade_map.count(lang) ? lang_upgrade_map[lang] : lang; +} + +namespace runp { +namespace fs = std::filesystem; +fs::path run_path; + +struct limits_t { + double time; + int memory; + int output; + double real_time; + int stack; + + limits_t() = default; + limits_t(const double &_time, const int &_memory, const int &_output) : + time(_time), memory(_memory), output(_output), real_time(-1), stack(-1) {} +}; + +// result type +enum RS_TYPE { + RS_AC = 0, + RS_WA = 1, + RS_RE = 2, + RS_MLE = 3, + RS_TLE = 4, + RS_OLE = 5, + RS_DGS = 6, + RS_JGF = 7 +}; + +inline std::string rstype_str(RS_TYPE id) { + switch (id) { + case RS_AC: + return "Accepted"; + case RS_WA: + return "Wrong Answer"; + case RS_RE: + return "Runtime Error"; + case RS_MLE: + return "Memory Limit Exceeded"; + case RS_TLE: + return "Time Limit Exceeded"; + case RS_OLE: + return "Output Limit Exceeded"; + case RS_DGS: + return "Dangerous Syscalls"; + case RS_JGF: + return "Judgment Failed"; + default: + return "Unknown Result"; + } +} + +inline std::string get_type_from_lang(std::string lang) { + lang = upgraded_lang(lang); + if (lang == "Python2") { + return "python2"; + } else if (lang == "Python3") { + return "python3"; + } else if (lang.size() >= 4 && lang.substr(0, 4) == "Java") { + return "java"; + } else { + return "default"; + } +} + +struct result { + static std::string result_file_name; + + RS_TYPE type; + std::string extra; + int ust, usm; + int exit_code; + + result() = default; + result(RS_TYPE type, std::string extra, int ust = -1, int usm = -1, int exit_code = -1) : + type(type), extra(extra), ust(ust), usm(usm), exit_code(exit_code) { + if (this->type != RS_AC) { + this->ust = -1, this->usm = -1; + } + } + + static result failed_result() { + result res; + res.type = RS_JGF; + res.ust = -1; + res.usm = -1; + return res; + } + + static result from_file(const std::string &file_name) { + result res; + FILE *fres = fopen(file_name.c_str(), "r"); + if (!fres) { + return result::failed_result(); + } + int type; + if (fscanf(fres, "%d %d %d %d\n", &type, &res.ust, &res.usm, &res.exit_code) != 4) { + fclose(fres); + return result::failed_result(); + } + res.type = (RS_TYPE)type; + + constexpr int L = 1 << 15; + char buf[L]; + while (!feof(fres)) { + int c = fread(buf, 1, L, fres); + res.extra.append(buf, c); + if (ferror(fres)) { + fclose(fres); + return result::failed_result(); + } + } + fclose(fres); + return res; + } + + [[noreturn]] void dump_and_exit() { + FILE *f; + if (result_file_name == "stdout") { + f = stdout; + } else if (result_file_name == "stderr") { + f = stderr; + } else { + f = fopen(result_file_name.c_str(), "w"); + } + fprintf(f, "%d %d %d %d\n", this->type, this->ust, this->usm, this->exit_code); + fprintf(f, "%s\n", this->extra.c_str()); + if (f != stdout && f != stderr) { + fclose(f); + } + exit(this->type == RS_JGF ? 1 : 0); + } +}; + +std::string result::result_file_name("stdout"); + +template +inline std::ostream &add_runp_arg(std::ostream &out, const std::pair> &arg) { + for (const auto &t : arg.second) { + out << " --" << arg.first << "=" << escapeshellarg(t); + } + return out; +} +template +inline std::ostream &add_runp_arg(std::ostream &out, const std::pair &arg) { + return out << " --" << arg.first << "=" << escapeshellarg(arg.second); +} +inline std::ostream &add_runp_arg(std::ostream &out, const std::vector &arg) { + for (const auto &t : arg) { + out << " " << escapeshellarg(t); + } + return out; +} +inline std::ostream &add_runp_arg(std::ostream &out, const std::string &arg) { + return out << " " << escapeshellarg(arg); +} + +struct config { + std::vector readable_file_names; // other than stdin + std::vector writable_file_names; // other than stdout, stderr + std::string result_file_name; + std::string input_file_name; + std::string output_file_name; + std::string error_file_name = "/dev/null"; + std::string type = "default"; + std::string work_path; + limits_t limits; + std::string program_name; + std::vector rest_args; + + // full args (possibly with interpreter) + std::vector full_args; + + bool unsafe = false; + bool allow_proc = false; + bool need_show_trace_details = false; + + config(std::string program_name = "", const std::vector &rest_args = {}) : + program_name(program_name), rest_args(rest_args) {} + + config &set_type(const std::string &type) { + this->type = type; + return *this; + } + + std::string get_cmd() const { + std::ostringstream sout; + sout << escapeshellarg(run_path / "run_program"); + + if (this->need_show_trace_details) { + add_runp_arg(sout, "--show-trace-details"); + } + + add_runp_arg(sout, std::make_pair("res", this->result_file_name)); + add_runp_arg(sout, std::make_pair("in", this->input_file_name)); + add_runp_arg(sout, std::make_pair("out", this->output_file_name)); + add_runp_arg(sout, std::make_pair("err", this->error_file_name)); + add_runp_arg(sout, std::make_pair("type", this->type)); + + // limits + add_runp_arg(sout, std::make_pair("tl", this->limits.time)); + add_runp_arg(sout, std::make_pair("ml", this->limits.memory)); + add_runp_arg(sout, std::make_pair("ol", this->limits.output)); + if (this->limits.real_time != -1) { + add_runp_arg(sout, std::make_pair("rtl", this->limits.real_time)); + } + if (this->limits.stack != -1) { + add_runp_arg(sout, std::make_pair("sl", this->limits.stack)); + } + + if (this->unsafe) { + add_runp_arg(sout, "--unsafe"); + } + if (this->allow_proc) { + add_runp_arg(sout, "--allow-proc"); + } + + if (!this->work_path.empty()) { + add_runp_arg(sout, std::make_pair("work-path", this->work_path)); + } + + add_runp_arg(sout, std::make_pair("add-readable", this->readable_file_names)); + add_runp_arg(sout, std::make_pair("add-writable", this->writable_file_names)); + + add_runp_arg(sout, this->program_name); + add_runp_arg(sout, this->rest_args); + + return sout.str(); + } + + void gen_full_args() { + // assume that current_path() == work_path + + full_args.clear(); + full_args.push_back(program_name); + full_args.insert(full_args.end(), rest_args.begin(), rest_args.end()); + + if (type == "java") { + full_args[0] = get_class_name_from_file(fs::path(full_args[0]) / ".main_class_name"); + + full_args.insert( + full_args.begin(), + {fs::canonical(fs::path(UOJ_JDK) / "bin" / "java"), "-Xmx2048m", "-Xss1024m", + "-XX:ActiveProcessorCount=1", "-classpath", program_name}); + } else if (type == "python2") { + full_args.insert(full_args.begin(), {UOJ_PYTHON2, "-E", "-s", "-B"}); + } else if (type == "python3") { + full_args.insert(full_args.begin(), {UOJ_PYTHON3, "-I", "-B"}); + } + } +}; + +/** + * @brief convert a time t to timeval. Assume t <= 1000. Accurate to ms. + * + * @param t time in double + * @return timeval + */ +timeval double_to_timeval(const double &t) { + long tl = round(t * 1000); + long tl_sec = tl / 1000; + long tl_usec = tl % 1000 * 1000; + return {tl_sec, tl_usec}; +} + +/** + * @brief convert a time t to timespec. Assume t <= 1000. Accurate to ms. + * + * @param t time in double + * @return timespec + */ +timespec double_to_timespec(const double &t) { + long tl = round(t * 1000); + long tl_sec = tl / 1000; + long tl_nsec = tl % 1000 * 1'000'000; + return {tl_sec, tl_nsec}; +} +} // namespace runp + +namespace runp::interaction { +struct pipe_config { + int from, from_fd; + int to, to_fd; + std::string saving_file_name; + + pipe_config() = default; + pipe_config(int _from, int _from_fd, int _to, int _to_fd, + const std::string &_saving_file_name = "") : + from(_from), + from_fd(_from_fd), + to(_to), + to_fd(_to_fd), + saving_file_name(_saving_file_name) {} + pipe_config(const std::string &str) { + if (sscanf(str.c_str(), "%d:%d-%d:%d", &from, &from_fd, &to, &to_fd) != 4) { + throw std::invalid_argument("bad init str for pipe"); + } + } +}; + +struct config { + std::vector cmds; + std::vector pipes; + + std::string get_cmd() const { + std::ostringstream sout; + sout << escapeshellarg(run_path / "run_interaction"); + for (auto &cmd : cmds) { + sout << " " << escapeshellarg(cmd); + } + for (auto &pipe : pipes) { + sout << " " << "-p"; + sout << " " << pipe.from << ":" << pipe.from_fd; + sout << "-" << pipe.to << ":" << pipe.to_fd; + + if (!pipe.saving_file_name.empty()) { + sout << " " << "-s"; + sout << " " << escapeshellarg(pipe.saving_file_name); + } + } + return sout.str(); + } +}; + +/* + * @return interaction return value + **/ +int run(const config &ric) { + return execute(ric.get_cmd().c_str()); +} +} // namespace runp::interaction diff --git a/judger/uoj_judger/include/uoj_secure.h b/judger/uoj_judger/include/uoj_secure.h new file mode 100644 index 000000000..7692f2a52 --- /dev/null +++ b/judger/uoj_judger/include/uoj_secure.h @@ -0,0 +1,331 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef _MSC_VER +#define UOJ_NORETURN __declspec(noreturn) +#elif defined __GNUC__ +#define UOJ_NORETURN __attribute__((noreturn)) +#else +#define UOJ_NORETURN +#endif + +namespace { +typedef unsigned char u8; +typedef unsigned u32; +typedef unsigned long long u64; + +using namespace std; + +struct sha256_t { + u8 sum[32]; + + string to_str() { + return string((char *)sum, 32); + } +}; + +inline u32 uoj_sha2_rotr(u32 x, int n) { + return x >> n | x << (32 - n); +} + +void uoj_sha256_chunk(u8 *chunk, u32 *hs) { + static const u32 k[] = {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + + u32 w[64]; + for (int i = 0; i < 16; i++) { + w[i] = ((u32)chunk[i << 2 | 3]) | ((u32)chunk[i << 2 | 2] << 8) + | ((u32)chunk[i << 2 | 1] << 16) | ((u32)chunk[i << 2] << 24); + } + + for (int i = 16; i < 64; i++) { + u32 s0 = uoj_sha2_rotr(w[i - 15], 7) ^ uoj_sha2_rotr(w[i - 15], 18) ^ (w[i - 15] >> 3); + u32 s1 = uoj_sha2_rotr(w[i - 2], 17) ^ uoj_sha2_rotr(w[i - 2], 19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + + u32 a = hs[0], b = hs[1], c = hs[2], d = hs[3], e = hs[4], f = hs[5], g = hs[6], h = hs[7]; + + for (int i = 0; i < 64; i++) { + u32 s1 = uoj_sha2_rotr(e, 6) ^ uoj_sha2_rotr(e, 11) ^ uoj_sha2_rotr(e, 25); + u32 ch = (e & f) ^ (~e & g); + u32 temp1 = h + s1 + ch + k[i] + w[i]; + u32 s0 = uoj_sha2_rotr(a, 2) ^ uoj_sha2_rotr(a, 13) ^ uoj_sha2_rotr(a, 22); + u32 maj = (a & b) ^ (a & c) ^ (b & c); + u32 temp2 = s0 + maj; + h = g; + g = f; + f = e; + e = d + temp1; + d = c; + c = b; + b = a; + a = temp1 + temp2; + } + + hs[0] += a, hs[1] += b, hs[2] += c, hs[3] += d, hs[4] += e, hs[5] += f, hs[6] += g, hs[7] += h; +} + +sha256_t uoj_sha256(int n, u8 *m) { + u32 hs[] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; + + u64 len = n * 8; + + int r_n = 0; + u8 r[128]; + for (int i = 0; i < n; i += 64) { + if (i + 64 <= n) { + uoj_sha256_chunk(m + i, hs); + } else { + for (int j = i; j < n; j++) { + r[r_n++] = m[j]; + } + } + } + + r[r_n++] = 0x80; + while ((r_n + 8) % 64 != 0) { + r[r_n++] = 0; + } + for (int i = 1; i <= 8; i++) { + r[r_n++] = len >> (64 - i * 8); + } + + for (int i = 0; i < r_n; i += 64) { + uoj_sha256_chunk(r + i, hs); + } + + sha256_t sum; + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 4; j++) { + sum.sum[i << 2 | j] = hs[i] >> (32 - (j + 1) * 8); + } + } + return sum; +} +sha256_t uoj_sha256(const string &m) { + return uoj_sha256((int)m.length(), (u8 *)m.data()); +} + +sha256_t uoj_hmac(const string &k, const string &m) { + string ki = k, ko = k; + for (int i = 0; i < (int)k.length(); i++) { + ki[i] ^= 0x36; + ko[i] ^= 0x5c; + } + return uoj_sha256(ko + uoj_sha256(ki + m).to_str()); +} + +class uoj_mt_rand_engine { + static const int N = 312; + static const int M = 156; + static const int R = 31; + static const u64 LM = (1llu << R) - 1; + static const u64 UM = ~LM; + static const u64 F = 6364136223846793005llu; + + u64 mt[N]; + int index; + + void init(u64 seed) { + index = N; + mt[0] = seed; + for (int i = 1; i < N; i++) { + mt[i] = F * (mt[i - 1] ^ (mt[i - 1] >> 62)) + i; + } + } + + void twist() { + for (int i = 0; i < N; i++) { + u64 x = (mt[i] & UM) + (mt[(i + 1) % N] & LM); + u64 xA = x >> 1; + if (x & 1) { + xA ^= 0xb5026f5aa96619e9llu; + } + mt[i] = mt[(i + M) % N] ^ xA; + } + index = 0; + } + +public: + uoj_mt_rand_engine(u64 seed) { + init(seed); + } + uoj_mt_rand_engine(const string &s) { + sha256_t sum = uoj_sha256(s); + + u64 seed = 0; + for (int i = 0; i < 8; i++) seed = seed << 8 | sum.sum[i]; + + init(seed); + } + + u64 next() { + if (index >= N) { + twist(); + } + + u64 y = mt[index]; + y ^= (y >> 29) & 0x5555555555555555llu; + y ^= (y << 17) & 0x71d67fffeda60000llu; + y ^= (y << 37) & 0xfff7eee000000000llu; + y ^= y >> 43; + + index++; + + return y; + } + + string randstr(int n, string charset = "0123456789abcdefghijklmnopqrstuvwxyz") { + string s; + for (int i = 0; i < n; i++) { + s += charset[next() % charset.length()]; + } + return s; + } +}; + +class uoj_cipher { + string key; + +public: + uoj_cipher() {} + uoj_cipher(const string &_key) : key(_key) {} + + void set_key(const string &_key) { + key = _key; + } + + void encrypt(string &m) { + uoj_mt_rand_engine rnd(key); + + string hmac = uoj_hmac(key, m).to_str(); + + m.push_back(0x80); + while ((m.length() + 32) % 512 != 0) { + m.push_back(0x00); + } + + m += hmac; + for (int i = 0; i < (int)m.length(); i += 8) { + u64 r = rnd.next(); + for (int j = i; j < i + 4; j++) { + m[j] = (u8)m[j] ^ (u8)r; + r >>= 16; + } + } + } + bool decrypt(string &m) { + uoj_mt_rand_engine rnd(key); + + if (m.empty() || m.length() % 512 != 0) { + return false; + } + for (int i = 0; i < (int)m.length(); i += 8) { + u64 r = rnd.next(); + for (int j = i; j < i + 4; j++) { + m[j] = (u8)m[j] ^ (u8)r; + r >>= 16; + } + } + string hmac = m.substr(m.length() - 32); + int len = m.length() - 33; + while (len >= 0 && (u8)m[len] != 0x80) { + len--; + } + if (len < 0) { + return false; + } + m.resize(len); + if (uoj_hmac(key, m).to_str() != hmac) { + return false; + } + return true; + } +}; + +class uoj_secure_io { + FILE fake_f, true_outf; + string input_m; + + string key; + uoj_cipher cipher; + +public: + istringstream in; + ostringstream out; + + uoj_secure_io() { + srand(time(NULL)); + + const int BUFFER_SIZE = 1024; + u8 buffer[BUFFER_SIZE + 1]; + while (!feof(stdin)) { + int ret = fread(buffer, 1, BUFFER_SIZE, stdin); + if (ret < 0) { + break; + } + input_m.append((char *)buffer, ret); + } + fclose(stdin); + + for (int i = 0; i < (int)sizeof(fake_f); i++) ((u8 *)&fake_f)[i] = rand(); + + memcpy(&true_outf, stdout, sizeof(FILE)); + memcpy(stdout, &fake_f, sizeof(FILE)); + } + + void init_with_key(const string &_key) { + cerr.tie(NULL); + + key = _key; + cipher.set_key(key); + + if (!cipher.decrypt(input_m)) { + end("Unauthorized input"); + } + + in.str(input_m); + } + + string input() { + return input_m; + } + + UOJ_NORETURN void end(string m) { + memcpy(stdout, &true_outf, sizeof(FILE)); + + if (!out.str().empty()) { + if (m.empty()) { + m = out.str(); + } else { + m = out.str() + m; + } + } + + cipher.encrypt(m); + fwrite(m.data(), 1, m.length(), stdout); + + fclose(stdout); + exit(0); + } + + UOJ_NORETURN void end() { + end(""); + } +}; +} // namespace diff --git a/judger/uoj_judger/main_judger.cpp b/judger/uoj_judger/main_judger.cpp index e9bdae3dd..31e6792f4 100644 --- a/judger/uoj_judger/main_judger.cpp +++ b/judger/uoj_judger/main_judger.cpp @@ -1,22 +1,27 @@ -#include "uoj_judger.h" +#include "uoj_judger_v2.h" -int main(int argc, char **argv) { - main_judger_init(argc, argv); - RunResult res = run_program( - (result_path + "/run_judger_result.txt").c_str(), - "/dev/null", - "/dev/null", - "stderr", - conf_run_limit("judger", 0, RL_JUDGER_DEFAULT), - "--unsafe", - conf_str("judger").c_str(), - main_path.c_str(), - work_path.c_str(), - result_path.c_str(), - data_path.c_str(), - NULL); - if (res.type != RS_AC) { - end_judge_judgement_failed("Judgement Failed : Judger " + info_str(res)); - } - return 0; +int main(int argc, char **argv) { + main_judger_init(argc, argv); + + runp::config rpc(conf_str("judger"), {main_path, work_path, result_path, data_path}); + rpc.result_file_name = result_path / "run_judger_result.txt"; + rpc.input_file_name = "/dev/null"; + rpc.output_file_name = "/dev/null"; + rpc.error_file_name = "stderr"; + rpc.limits = conf_run_limit("judger", 0, RL_JUDGER_DEFAULT); + rpc.unsafe = true; + runp::result res = runp::run(rpc); + + if (res.type != runp::RS_AC) { + stringstream msg; + + msg << "Judgment Failed" << endl; + msg << "\n------\n" << endl; + msg << "Judger result: " << runp::rstype_str(res.type) << endl; + msg << "Extra message: " << res.extra << endl; + + end_judge_judgment_failed(msg.str()); + } + + return 0; } diff --git a/judger/uoj_judger/run/compile.cpp b/judger/uoj_judger/run/compile.cpp new file mode 100644 index 000000000..4e4dc7782 --- /dev/null +++ b/judger/uoj_judger/run/compile.cpp @@ -0,0 +1,509 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uoj_run.h" + +namespace fs = std::filesystem; + +class language_not_supported_error : public std::invalid_argument { +public: + explicit language_not_supported_error() : + std::invalid_argument("This language has not been supported yet.") {} +}; + +class fail_to_read_src_error : public std::runtime_error { +public: + explicit fail_to_read_src_error( + const std::string &what = "An error occurs when trying to read the source code.") : + std::runtime_error(what) {} +}; + +class compile_error : public std::invalid_argument { +public: + explicit compile_error(const std::string &what) : std::invalid_argument(what) {} +}; + +const std::vector> suffix_search_list = { + {".code", ""}, + // C++ + {"98.cpp", "C++98"}, + {"03.cpp", "C++03"}, + {"11.cpp", "C++11"}, + {"14.cpp", "C++14"}, + {"17.cpp", "C++17"}, + {"20.cpp", "C++20"}, + {"23.cpp", "C++23"}, + {"26.cpp", "C++26"}, + {".cpp", "C++17"}, + // C + {"89.c", "C89"}, + {"99.c", "C99"}, + {"11.c", "C11"}, + {"17.c", "C17"}, + {"23.c", "C23"}, + {".c", "C17"}, + // Java + {"8.java", "Java8"}, + {"14.java", "Java14"}, + {"17.java", "Java17"}, + {"21.java", "Java21"}, + // Others + {".pas", "Pascal"}, + {"2.py", "Python2"}, + {".py", "Python3"}, +}; + +struct compile_config { + std::string name; + std::string src; + std::string lang = "auto"; + std::string opt; + std::string implementer; + std::string custom_compiler_path; + std::vector cinclude_dirs; + + void auto_find_src() { + if (!src.empty()) { + return; + } + for (auto &p : suffix_search_list) { + if (fs::is_regular_file(name + p.first)) { + src = fs::canonical(name + p.first); + if (lang == "auto") { + lang = p.second; + } + return; + } + } + } +}; + +error_t compile_argp_parse_opt(int key, char *arg, struct argp_state *state) { + compile_config *config = (compile_config *)state->input; + + try { + switch (key) { + case 's': + config->src = arg; + break; + case 'i': + config->implementer = arg; + break; + case 'l': + config->lang = arg; + break; + case 'c': + config->custom_compiler_path = arg; + break; + case 'I': + config->cinclude_dirs.push_back(arg); + break; + case ARGP_KEY_ARG: + config->name = arg; + break; + case ARGP_KEY_END: + if (state->arg_num != 1) { + argp_usage(state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + } catch (std::exception &e) { + argp_usage(state); + } + + return 0; +} + +compile_config parse_args(int argc, char **argv) { + argp_option argp_options[] = { + {"src", 's', "SOURCE_CODE", 0, "set the path to source code", 1}, + {"impl", 'i', "IMPLEMENTER", 0, "set the implementer name", 2}, + {"lang", 'l', "LANGUAGE", 0, "set the language", 3}, + {"custom", 'c', "CUSTOM", 0, "path to custom compilers (those are not placed in /usr/bin)", + 4}, + {"cinclude", 'I', "DIRECTORY", 0, + "add the directory dir to the list of directories to be searched for header files during " + "preprocessing (for C/C++)", + 5}, + {0}}; + char argp_args_doc[] = "name"; + char argp_doc[] = "compile: a tool to compile programs"; + + argp compile_argp = {argp_options, compile_argp_parse_opt, argp_args_doc, argp_doc}; + + compile_config config; + argp_parse(&compile_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &config); + + config.auto_find_src(); + if (config.src.empty() || !fs::is_regular_file(config.src)) { + throw fail_to_read_src_error(); + } + + return config; +} + +bool is_illegal_keyword(const std::string &name) { + return name == "__asm" || name == "__asm__" || name == "asm"; +} + +bool has_illegal_keywords_in_file(const std::string &src) { + std::ifstream fin(src); + if (!fin) { + throw fail_to_read_src_error(); + } + + const int L = 1 << 15; + char buf[L]; + std::string key; + + while (!fin.eof()) { + fin.read(buf, L); + int cnt = fin.gcount(); + for (char *p = buf; p != buf + cnt; p++) { + char c = *p; + if (isalnum(c) || c == '_') { + if (key.size() < 20) { + key += c; + } else { + if (is_illegal_keyword(key)) { + return true; + } + key.erase(key.begin()); + key += c; + } + } else { + if (is_illegal_keyword(key)) { + return true; + } + key.clear(); + } + } + if (fin.bad()) { + throw fail_to_read_src_error(); + } + } + return false; +} + +std::string get_java_main_class(const std::string &src) { + std::ifstream fin(src); + if (!fin) { + throw fail_to_read_src_error(); + } + + const int L = 1 << 15; + char buf[L]; + std::string s; + + int mode = 0; + + while (!fin.eof()) { + fin.read(buf, L); + int cnt = fin.gcount(); + for (char *p = buf; p != buf + cnt; p++) { + s += *p; + switch (mode) { + case 0: + switch (*p) { + case '/': + mode = 1; + break; + case '\'': + mode = 5; + break; + case '\"': + mode = 6; + break; + } + break; + case 1: + switch (*p) { + case '/': + mode = 2; + s.pop_back(); + s.pop_back(); + break; + case '*': + mode = 3; + s.pop_back(); + s.pop_back(); + break; + } + break; + case 2: + s.pop_back(); + switch (*p) { + case '\n': + s += '\n'; + mode = 0; + break; + } + break; + case 3: + s.pop_back(); + switch (*p) { + case '*': + mode = 4; + break; + } + break; + case 4: + s.pop_back(); + switch (*p) { + case '/': + s += ' '; + mode = 0; + break; + } + break; + case 5: + switch (*p) { + case '\'': + mode = 0; + break; + case '\\': + mode = 7; + break; + } + case 6: + switch (*p) { + case '\"': + mode = 0; + break; + case '\\': + mode = 8; + break; + } + case 7: + mode = 5; + break; + case 8: + mode = 6; + break; + } + } + if (fin.bad()) { + throw fail_to_read_src_error(); + } + } + + bool valid[256]; + std::fill(valid, valid + 256, false); + std::fill(valid + 'a', valid + 'z' + 1, true); + std::fill(valid + 'A', valid + 'Z' + 1, true); + std::fill(valid + '0', valid + '9' + 1, true); + valid['.'] = true; + valid['_'] = true; + + std::vector tokens; + for (size_t p = 0, np = 0; p < s.length(); p = np) { + while (np < s.length() && valid[(unsigned char)s[np]]) { + np++; + } + if (np == p) { + np++; + } else { + tokens.push_back(s.substr(p, np - p)); + } + } + if (tokens.size() > 0 && tokens[0] == "package") { + throw compile_error("Please don't specify the package."); + } + + for (size_t i = 0; i + 1 < tokens.size(); i++) { + if (tokens[i] == "class") { + std::string name = tokens[i + 1]; + if (name.length() > 100) { + throw compile_error("The name of the main class is too long."); + } + for (size_t k = 0; k < name.length(); k++) { + if (!isalnum(name[k]) && name[k] != '_') { + throw compile_error( + "The name of the main class should only contain letters, numbers and " + "underscore sign."); + } + } + if (!isalpha(name[0])) { + throw compile_error("The name of the main class cannot begin with a number."); + } + return tokens[i + 1]; + } + } + + throw compile_error("Cannot find the main class."); +} + +int compile_cpp(const compile_config &conf, const std::string &std) { + std::ostringstream sflags; + spaced_out(sflags, "-lm", "-O2", "-DONLINE_JUDGE", "-std=" + std); + for (auto dir : conf.cinclude_dirs) { + add_spaced_out(sflags, "-I" + dir); + } + + if (conf.implementer.empty()) { + return execute(UOJ_GPLUSPLUS, sflags.str(), "-o", conf.name, "-x", "c++", conf.src); + } else { + return execute(UOJ_GPLUSPLUS, sflags.str(), "-o", conf.name, conf.implementer + ".cpp", + "-x", "c++", conf.src); + } +} +int compile_c(const compile_config &conf, const std::string &std) { + std::ostringstream sflags; + spaced_out(sflags, "-lm", "-O2", "-DONLINE_JUDGE", "-std=" + std); + for (auto dir : conf.cinclude_dirs) { + add_spaced_out(sflags, "-I" + dir); + } + + if (conf.implementer.empty()) { + return execute(UOJ_GCC, sflags.str(), "-o", conf.name, "-x", "c", conf.src); + } else { + return execute(UOJ_GCC, sflags.str(), "-o", conf.name, conf.implementer + ".c", "-x", "c", + conf.src); + } +} +int compile_python2_7(const compile_config &conf) { + if (!conf.implementer.empty()) { + throw language_not_supported_error(); + } + + std::string dfile = "__pycode__/" + conf.name + ".py"; + std::string compiler_code = + "import py_compile\n" + "import sys\n" + "try:\n" + " py_compile.compile('" + conf.src + "', cfile='" + conf.name + "', dfile='" + dfile + "', doraise=True)\n" + " sys.exit(0)\n" + "except Exception as e:\n" + " print e\n" + " sys.exit(1)\n"; + return execute(UOJ_PYTHON2, "-E", "-s", "-B", "-O", "-c", escapeshellarg(compiler_code)); +} +int compile_python3(const compile_config &conf) { + if (!conf.implementer.empty()) { + throw language_not_supported_error(); + } + std::string dfile = "__pycode__/" + conf.name + ".py"; + std::string compiler_code = + "import py_compile\n" + "import sys\n" + "try:\n" + " py_compile.compile('" + conf.src + "', cfile='" + conf.name + "', dfile='" + dfile + "', doraise=True)\n" + " sys.exit(0)\n" + "except Exception as e:\n" + " print(e)\n" + " sys.exit(1)\n"; + return execute(UOJ_PYTHON3, "-I", "-B", "-O", "-c", escapeshellarg(compiler_code)); +} +int compile_java(const compile_config &conf, const std::string &version) { + if (!conf.implementer.empty()) { + throw language_not_supported_error(); + } + + try { + std::string main_class = get_java_main_class(conf.src); + fs::remove_all(conf.name); + fs::create_directory(conf.name); + fs::copy_file(conf.src, fs::path(conf.name) / (main_class + ".java")); + int ret = execute("cd", conf.name, "&&", UOJ_JDK "/bin/javac", "--release", version, + main_class + ".java"); + fs::remove(fs::path(conf.name) / (main_class + ".java")); + put_class_name_to_file(fs::path(conf.name) / ".main_class_name", main_class); + return ret; + } catch (std::system_error &e) { + throw compile_error("System Error"); + } +} +int compile_pas(const compile_config &conf) { + if (conf.implementer.empty()) { + fs::path src_path(conf.src); + fs::path dir = src_path.parent_path(); + fs::path file = src_path.filename(); + return execute("cd", dir.string(), "&&", UOJ_FPC, file.string(), "-O2"); + } else { + try { + std::string unit_name = get_class_name_from_file(conf.name + ".unit_name"); + if (!unit_name.empty()) { + fs::copy_file(conf.src, unit_name + ".pas"); + } + int ret = execute(UOJ_FPC, conf.implementer + ".pas", "-o" + conf.name, "-O2"); + if (!unit_name.empty()) { + fs::remove(unit_name + ".pas"); + } + return ret; + } catch (std::system_error &e) { + throw compile_error("System Error"); + } + } +} + +int compile(const compile_config &conf) { + std::string lang = upgraded_lang(conf.lang); + + if ((lang.length() > 0 && lang[0] == 'C') && has_illegal_keywords_in_file(conf.src)) { + std::cerr << "Compile Failed: assembly language detected" << std::endl; + return 1; + } + + if (lang == "C++98") { + return compile_cpp(conf, "c++98"); + } else if (lang == "C++03") { + return compile_cpp(conf, "c++03"); + } else if (lang == "C++11") { + return compile_cpp(conf, "c++11"); + } else if (lang == "C++14") { + return compile_cpp(conf, "c++14"); + } else if (lang == "C++17") { + return compile_cpp(conf, "c++17"); + } else if (lang == "C++20") { + return compile_cpp(conf, "c++20"); + } else if (lang == "C++23") { + return compile_cpp(conf, "c++23"); + } else if (lang == "C++26") { + return compile_cpp(conf, "c++26"); + } else if (lang == "C89") { + return compile_c(conf, "c89"); + } else if (lang == "C99") { + return compile_c(conf, "c99"); + } else if (lang == "C11") { + return compile_c(conf, "c11"); + } else if (lang == "C17") { + return compile_c(conf, "c17"); + } else if (lang == "C23") { + return compile_c(conf, "c23"); + } else if (lang == "Python2") { + return compile_python2_7(conf); + } else if (lang == "Python3") { + return compile_python3(conf); + } else if (lang == "Java8") { + return compile_java(conf, "8"); + } else if (lang == "Java11") { + return compile_java(conf, "11"); + } else if (lang == "Java17") { + return compile_java(conf, "17"); + } else if (lang == "Java21") { + return compile_java(conf, "21"); + } else if (lang == "Pascal") { + return compile_pas(conf); + } else { + throw language_not_supported_error(); + } +} + +int main(int argc, char **argv) { + try { + return compile(parse_args(argc, argv)); + } catch (std::exception &e) { + std::cerr << e.what() << std::endl; + return 1; + } +} diff --git a/judger/uoj_judger/run/formatter.cpp b/judger/uoj_judger/run/formatter.cpp index 6dcbf1403..8d983958d 100644 --- a/judger/uoj_judger/run/formatter.cpp +++ b/judger/uoj_judger/run/formatter.cpp @@ -1,55 +1,31 @@ -#include -#include -#include -#include -#include -using namespace std; -typedef long long LL; -int main() -{ - //freopen("1.in","r",stdin); - char c,last; - int nSpace=0,nR=0,first; - while(1) - { - last=c,c=getchar(); - if(c==EOF) - { - if(last!='\n') +#include +#include +#include +#include +#include + +int main() { + int c = '?', last; + std::string buf; + while (true) { + last = c; + c = getchar(); + if (c == EOF) { + if (last != '\n') { putchar('\n'); - break; - } - else if(c!='\r'&&c!=' ') - { - if(c!='\n'&&first==0) - { - for(int j=1;j<=nSpace;++j) - putchar(' '); - for(int j=1;j<=nR;++j) - putchar('\r'); } - else if(c!='\n') - { - for(int j=1;j<=nR;++j) - putchar('\r'); - for(int j=1;j<=nSpace;++j) - putchar(' '); + break; + } else if (c == ' ' || c == '\r') { + buf.push_back((char)c); + } else { + if (!buf.empty()) { + if (c != '\n') { + printf("%s", buf.c_str()); + } + buf.clear(); } - nSpace=nR=0; putchar(c); } - else if(c==' ') - { - ++nSpace; - if(nR==0) - first=0; - } - else - { - ++nR; - if(nSpace==0) - first=1; - } } return 0; } diff --git a/judger/uoj_judger/run/run_interaction.cpp b/judger/uoj_judger/run/run_interaction.cpp index 7c9ca1580..42f32f514 100644 --- a/judger/uoj_judger/run/run_interaction.cpp +++ b/judger/uoj_judger/run/run_interaction.cpp @@ -1,263 +1,266 @@ -#include -#include -#include -#include -#include -#include -#include +#include #include +#include #include -#include #include #include -#include +#include +#include + +#include +#include #include +#include +#include #include +#include +#include #include -#include -#include -#include "uoj_env.h" -using namespace std; -struct RunResult { - int result; - int ust; - int usm; - int exit_code; - - RunResult(int _result, int _ust = -1, int _usm = -1, int _exit_code = -1) - : result(_result), ust(_ust), usm(_usm), exit_code(_exit_code) { - if (result != RS_AC) { - ust = -1, usm = -1; - } - } -}; +#include "uoj_run.h" -struct PipeConfig { - int from, to; - int from_fd, to_fd; - - string saving_file_name; // empty for none - - PipeConfig() { - } - PipeConfig(string str) { - if (sscanf(str.c_str(), "%d:%d-%d:%d", &from, &from_fd, &to, &to_fd) != 4) { - throw invalid_argument("bad init str for PipeConfig"); - } - from -= 1; - to -= 1; - } -}; - -struct RunInteractionConfig { - vector cmds; - vector pipes; -}; +using namespace std; -struct RunCmdData { - string cmd; - pid_t pid; +struct run_cmd_data { + string cmd; + pid_t pid; - vector ipipes, opipes; + vector ipipes, opipes; }; -struct PipeData { - PipeConfig config; - int ipipefd[2], opipefd[2]; - thread io_thread; - exception_ptr eptr; +struct pipe_data { + runp::interaction::pipe_config config; + int ipipefd[2], opipefd[2]; + thread io_thread; + exception_ptr eptr; }; -class RunInteraction { +void write_all_or_throw(int fd, char *buf, int n) { + int wcnt = 0; + while (wcnt < n) { + int ret = write(fd, buf + wcnt, n - wcnt); + if (ret == -1) { + throw system_error(errno, system_category()); + } + wcnt += ret; + } +} + +class interaction_runner { private: - vector cmds; - vector pipes; - - void prepare_fd() { // me - for (int i = 0; i < (int)pipes.size(); i++) { - close(pipes[i].ipipefd[1]); - close(pipes[i].opipefd[0]); - } - } - void prepare_fd_for_cmd(int id) { - freopen("/dev/null", "r", stdin); - freopen("/dev/null", "w", stdout); - freopen("/dev/null", "w", stderr); - - for (int i = 0; i < (int)pipes.size(); i++) { - if (pipes[i].config.from == id) { - dup2(pipes[i].ipipefd[1], 128 + pipes[i].ipipefd[1]); - } - if (pipes[i].config.to == id) { - dup2(pipes[i].opipefd[0], 128 + pipes[i].opipefd[0]); - } - close(pipes[i].ipipefd[0]); - close(pipes[i].ipipefd[1]); - close(pipes[i].opipefd[0]); - close(pipes[i].opipefd[1]); - } - for (int i = 0; i < (int)pipes.size(); i++) { - if (pipes[i].config.from == id) { - dup2(128 + pipes[i].ipipefd[1], pipes[i].config.from_fd); - close(128 + pipes[i].ipipefd[1]); - } - if (pipes[i].config.to == id) { - dup2(128 + pipes[i].opipefd[0], pipes[i].config.to_fd); - close(128 + pipes[i].opipefd[0]); - } - } - } - - void wait_pipe_io(int pipe_id) { - FILE *sf = NULL; - if (!pipes[pipe_id].config.saving_file_name.empty()) - sf = fopen(pipes[pipe_id].config.saving_file_name.c_str(), "w"); - - int ifd = pipes[pipe_id].ipipefd[0]; - int ofd = pipes[pipe_id].opipefd[1]; - - FILE *inf = fdopen(ifd, "r"); - FILE *ouf = fdopen(ofd, "w"); - - try { - pipes[pipe_id].eptr = nullptr; - - const int L = 4096; - char buf[L]; - - while (true) { - int c = fgetc(inf); - if (c == EOF) { - if (errno) { - throw system_error(errno, system_category()); - } - break; - } - - if (fputc(c, ouf) == EOF) { - throw system_error(errno, system_category()); - } - fflush(ouf); - - if (fputc(c, sf) == EOF) { - throw system_error(errno, system_category()); - } - } - } catch (exception &e) { - cerr << e.what() << endl; - pipes[pipe_id].eptr = current_exception(); - } - - fclose(sf); - fclose(inf); - fclose(ouf); - } + vector cmds; + vector pipes; + + void prepare_fd() { // me + for (int i = 0; i < (int)pipes.size(); i++) { + close(pipes[i].ipipefd[1]); + close(pipes[i].opipefd[0]); + } + } + void prepare_fd_for_cmd(int id) { + if (freopen("/dev/null", "r", stdin) == NULL) { + throw system_error(errno, system_category()); + } + if (freopen("/dev/null", "w", stdout) == NULL) { + throw system_error(errno, system_category()); + } + if (freopen("/dev/null", "w", stderr) == NULL) { + throw system_error(errno, system_category()); + } + + for (int i = 0; i < (int)pipes.size(); i++) { + if (pipes[i].config.from - 1 == id) { + dup2(pipes[i].ipipefd[1], 128 + pipes[i].ipipefd[1]); + } + if (pipes[i].config.to - 1 == id) { + dup2(pipes[i].opipefd[0], 128 + pipes[i].opipefd[0]); + } + close(pipes[i].ipipefd[0]); + close(pipes[i].ipipefd[1]); + close(pipes[i].opipefd[0]); + close(pipes[i].opipefd[1]); + } + for (int i = 0; i < (int)pipes.size(); i++) { + if (pipes[i].config.from - 1 == id) { + dup2(128 + pipes[i].ipipefd[1], pipes[i].config.from_fd); + close(128 + pipes[i].ipipefd[1]); + } + if (pipes[i].config.to - 1 == id) { + dup2(128 + pipes[i].opipefd[0], pipes[i].config.to_fd); + close(128 + pipes[i].opipefd[0]); + } + } + } + + void wait_pipe_io(int pipe_id) { + FILE *sf = nullptr; + if (!pipes[pipe_id].config.saving_file_name.empty()) { + sf = fopen(pipes[pipe_id].config.saving_file_name.c_str(), "w"); + } + int ifd = pipes[pipe_id].ipipefd[0]; + int ofd = pipes[pipe_id].opipefd[1]; + int sfd = sf ? fileno(sf) : -1; + + int iflags = fcntl(ifd, F_GETFL); + + const int L = 4096; + char buf[L]; + + int sbuf_len = 0; + char sbuf[L * 2]; + + try { + pipes[pipe_id].eptr = nullptr; + + while (true) { + int cnt1 = read(ifd, buf, 1); + if (cnt1 == -1) { + throw system_error(errno, system_category()); + } + if (cnt1 == 0) { + break; + } + + fcntl(ifd, F_SETFL, iflags | O_NONBLOCK); + int cnt2 = read(ifd, buf + 1, L - 1); + fcntl(ifd, F_SETFL, iflags); + + if (cnt2 == -1) { + if (errno != EAGAIN) { + throw system_error(errno, system_category()); + } + cnt2 = 0; + } + + write_all_or_throw(ofd, buf, cnt2 + 1); + + if (sf) { + memcpy(sbuf + sbuf_len, buf, cnt2 + 1); + sbuf_len += cnt2 + 1; + if (sbuf_len > L) { + write_all_or_throw(sfd, sbuf, sbuf_len); + sbuf_len = 0; + } + } + } + } catch (exception &e) { + cerr << e.what() << endl; + pipes[pipe_id].eptr = current_exception(); + } + + if (sf) { + if (sbuf_len > 0) { + write_all_or_throw(sfd, sbuf, sbuf_len); + sbuf_len = 0; + } + fclose(sf); + } + + close(ifd); + close(ofd); + } + public: - RunInteraction(const RunInteractionConfig &config) { - cmds.resize(config.cmds.size()); - for (int i = 0; i < (int)config.cmds.size(); i++) { - cmds[i].cmd = config.cmds[i]; - } - - pipes.resize(config.pipes.size()); - for (int i = 0; i < (int)config.pipes.size(); i++) { - pipes[i].config = config.pipes[i]; - cmds[pipes[i].config.from].opipes.push_back(i); - cmds[pipes[i].config.to].ipipes.push_back(i); - } - - for (int i = 0; i < (int)pipes.size(); i++) { - if (pipe(pipes[i].ipipefd) == -1 || pipe(pipes[i].opipefd) == -1) { - throw system_error(errno, system_category()); - } - } - for (int i = 0; i < (int)cmds.size(); i++) { - cmds[i].pid = fork(); - if (cmds[i].pid == 0) { - prepare_fd_for_cmd(i); - system(cmds[i].cmd.c_str()); - exit(0); - } else if (cmds[i].pid == -1) { - throw system_error(errno, system_category()); - } - } - - prepare_fd(); - for (int i = 0; i < (int)pipes.size(); i++) { - pipes[i].io_thread = thread(&RunInteraction::wait_pipe_io, this, i); - } - } - - void join() { - int status; - while (wait(&status) > 0); - - for (int i = 0; i < (int)pipes.size(); i++) { - pipes[i].io_thread.join(); - } - } + interaction_runner(const runp::interaction::config &config) { + cmds.resize(config.cmds.size()); + for (int i = 0; i < (int)config.cmds.size(); i++) { + cmds[i].cmd = config.cmds[i]; + } + + pipes.resize(config.pipes.size()); + for (int i = 0; i < (int)config.pipes.size(); i++) { + pipes[i].config = config.pipes[i]; + cmds[pipes[i].config.from - 1].opipes.push_back(i); + cmds[pipes[i].config.to - 1].ipipes.push_back(i); + } + + for (int i = 0; i < (int)pipes.size(); i++) { + if (pipe(pipes[i].ipipefd) == -1 || pipe(pipes[i].opipefd) == -1) { + throw system_error(errno, system_category()); + } + } + for (int i = 0; i < (int)cmds.size(); i++) { + cmds[i].pid = fork(); + if (cmds[i].pid == 0) { + prepare_fd_for_cmd(i); + execl("/bin/sh", "sh", "-c", cmds[i].cmd.c_str(), NULL); + exit(1); // exec failed, exit 1 + } else if (cmds[i].pid == -1) { + throw system_error(errno, system_category()); + } + } + + prepare_fd(); + for (int i = 0; i < (int)pipes.size(); i++) { + pipes[i].io_thread = thread(&interaction_runner::wait_pipe_io, this, i); + } + } + + void join() { + int status; + while (wait(&status) > 0); + + for (int i = 0; i < (int)pipes.size(); i++) { + pipes[i].io_thread.join(); + } + } }; -error_t run_interaction_argp_parse_opt (int key, char *arg, struct argp_state *state) { - RunInteractionConfig *config = (RunInteractionConfig*)state->input; - - try { - switch (key) { - case 'p': - config->pipes.push_back(PipeConfig(arg)); - break; - case 's': - if (config->pipes.empty()) { - argp_usage(state); - } - config->pipes.back().saving_file_name = arg; - break; - case ARGP_KEY_ARG: - config->cmds.push_back(arg); - break; - case ARGP_KEY_END: - if (state->arg_num == 0) { - argp_usage(state); - } - break; - default: - return ARGP_ERR_UNKNOWN; - } - } catch (exception &e) { - argp_usage(state); - } - - return 0; +error_t run_interaction_argp_parse_opt(int key, char *arg, struct argp_state *state) { + runp::interaction::config *config = (runp::interaction::config *)state->input; + + try { + switch (key) { + case 'p': + config->pipes.push_back(runp::interaction::pipe_config(arg)); + break; + case 's': + if (config->pipes.empty()) { + argp_usage(state); + } + config->pipes.back().saving_file_name = arg; + break; + case ARGP_KEY_ARG: + config->cmds.push_back(arg); + break; + case ARGP_KEY_END: + if (state->arg_num == 0) { + argp_usage(state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + } catch (exception &e) { + argp_usage(state); + } + + return 0; } -RunInteractionConfig parse_args(int argc, char **argv) { - argp_option run_interaction_argp_options[] = { - {"add-pipe" , 'p', "PIPE" , 0, "Add a pipe :-: (fd < 128)" , 1}, - {"save-pipe" , 's', "FILE" , 0, "Set last pipe saving file" , 2}, - {0} - }; - char run_interaction_argp_args_doc[] = "cmd1 cmd2 ..."; - char run_interaction_argp_doc[] = "run_interaction: a tool to run multiple commands with interaction"; +runp::interaction::config parse_args(int argc, char **argv) { + argp_option run_interaction_argp_options[] = { + {"add-pipe", 'p', "PIPE", 0, "Add a pipe :-: (fd < 128)", 1}, + {"save-pipe", 's', "FILE", 0, "Set last pipe saving file", 2}, + {0}}; + char run_interaction_argp_args_doc[] = "cmd1 cmd2 ..."; + char run_interaction_argp_doc[] = + "run_interaction: a tool to run multiple commands with interaction"; - argp run_interaction_argp = { - run_interaction_argp_options, - run_interaction_argp_parse_opt, - run_interaction_argp_args_doc, - run_interaction_argp_doc - }; + argp run_interaction_argp = {run_interaction_argp_options, run_interaction_argp_parse_opt, + run_interaction_argp_args_doc, run_interaction_argp_doc}; - RunInteractionConfig config; + runp::interaction::config config; - argp_parse(&run_interaction_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &config); + argp_parse(&run_interaction_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &config); - return config; + return config; } int main(int argc, char **argv) { - signal(SIGPIPE, SIG_IGN); + signal(SIGPIPE, SIG_IGN); - RunInteraction ri(parse_args(argc, argv)); - ri.join(); + interaction_runner ri(parse_args(argc, argv)); + ri.join(); - return 0; + return 0; } diff --git a/judger/uoj_judger/run/run_program.cpp b/judger/uoj_judger/run/run_program.cpp index f7ebea267..77d0c035d 100644 --- a/judger/uoj_judger/run/run_program.cpp +++ b/judger/uoj_judger/run/run_program.cpp @@ -1,581 +1,696 @@ -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "uoj_env.h" -using namespace std; - -struct RunResult { - int result; - int ust; - int usm; - int exit_code; - - RunResult(int _result, int _ust = -1, int _usm = -1, int _exit_code = -1) - : result(_result), ust(_ust), usm(_usm), exit_code(_exit_code) { - if (result != RS_AC) { - ust = -1, usm = -1; - } - } -}; - -struct RunProgramConfig -{ - int time_limit; - int real_time_limit; - int memory_limit; - int output_limit; - int stack_limit; - string input_file_name; - string output_file_name; - string error_file_name; - string result_file_name; - string work_path; - string type; - vector extra_readable_files; - vector extra_writable_files; - bool allow_proc; - bool safe_mode; - bool need_show_trace_details; - - string program_name; - string program_basename; - vector argv; +#include "run_program_sandbox.h" + +namespace fs = std::filesystem; + +enum RUN_EVENT_TYPE { + ET_SKIP, + ET_EXIT, + ET_SIGNALED, + ET_REAL_TLE, + ET_USER_CPU_TLE, + ET_MLE, + ET_OLE, + ET_SECCOMP_STOP, + ET_SIGNAL_DELIVERY_STOP, + ET_RESTART, }; -int put_result(string result_file_name, RunResult res) { - FILE *f; - if (result_file_name == "stdout") { - f = stdout; - } else if (result_file_name == "stderr") { - f = stderr; - } else { - f = fopen(result_file_name.c_str(), "w"); - } - fprintf(f, "%d %d %d %d\n", res.result, res.ust, res.usm, res.exit_code); - if (f != stdout && f != stderr) { - fclose(f); - } - if (res.result == RS_JGF) { - return 1; - } else { - return 0; - } -} +struct run_event { + RUN_EVENT_TYPE type; + int pid = -1; + rp_child_proc *cp; -char self_path[PATH_MAX + 1] = {}; - -#include "run_program_conf.h" - -argp_option run_program_argp_options[] = -{ - {"tl" , 'T', "TIME_LIMIT" , 0, "Set time limit (in second)" , 1}, - {"rtl" , 'R', "TIME_LIMIT" , 0, "Set real time limit (in second)" , 2}, - {"ml" , 'M', "MEMORY_LIMIT", 0, "Set memory limit (in mb)" , 3}, - {"ol" , 'O', "OUTPUT_LIMIT", 0, "Set output limit (in mb)" , 4}, - {"sl" , 'S', "STACK_LIMIT" , 0, "Set stack limit (in mb)" , 5}, - {"in" , 'i', "IN" , 0, "Set input file name" , 6}, - {"out" , 'o', "OUT" , 0, "Set output file name" , 7}, - {"err" , 'e', "ERR" , 0, "Set error file name" , 8}, - {"work-path" , 'w', "WORK_PATH" , 0, "Set the work path of the program" , 9}, - {"type" , 't', "TYPE" , 0, "Set the program type (for some program such as python)", 10}, - {"res" , 'r', "RESULT_FILE" , 0, "Set the file name for outputing the result ", 10}, - {"add-readable" , 500, "FILE" , 0, "Add a readable file" , 11}, - {"add-writable" , 505, "FILE" , 0, "Add a writable file" , 11}, - {"unsafe" , 501, 0 , 0, "Don't check dangerous syscalls" , 12}, - {"show-trace-details" , 502, 0 , 0, "Show trace details" , 13}, - {"allow-proc" , 503, 0 , 0, "Allow fork, exec... etc." , 14}, - {"add-readable-raw" , 504, "FILE" , 0, "Add a readable (don't transform to its real path)" , 15}, - {"add-writable-raw" , 506, "FILE" , 0, "Add a writable (don't transform to its real path)" , 15}, - {0} + int sig = 0; + int exitcode = 0; + int pevent = 0; + + int usertim = 0, usermem = 0; }; -error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state) -{ - RunProgramConfig *config = (RunProgramConfig*)state->input; - - switch (key) - { - case 'T': - config->time_limit = atoi(arg); - break; - case 'R': - config->real_time_limit = atoi(arg); - break; - case 'M': - config->memory_limit = atoi(arg); - break; - case 'O': - config->output_limit = atoi(arg); - break; - case 'S': - config->stack_limit = atoi(arg); - break; - case 'i': - config->input_file_name = arg; - break; - case 'o': - config->output_file_name = arg; - break; - case 'e': - config->error_file_name = arg; - break; - case 'w': - config->work_path = realpath(arg); - if (config->work_path.empty()) { - argp_usage(state); - } - break; - case 'r': - config->result_file_name = arg; - break; - case 't': - config->type = arg; - break; - case 500: - config->extra_readable_files.push_back(realpath(arg)); - break; - case 501: - config->safe_mode = false; - break; - case 502: - config->need_show_trace_details = true; - break; - case 503: - config->allow_proc = true; - break; - case 504: - config->extra_readable_files.push_back(arg); - break; - case 505: - config->extra_writable_files.push_back(realpath(arg)); - break; - case 506: - config->extra_writable_files.push_back(arg); - break; - case ARGP_KEY_ARG: - config->argv.push_back(arg); - for (int i = state->next; i < state->argc; i++) { - config->argv.push_back(state->argv[i]); - } - state->next = state->argc; - break; - case ARGP_KEY_END: - if (state->arg_num == 0) { - argp_usage(state); - } - break; - default: - return ARGP_ERR_UNKNOWN; - } - return 0; + +argp_option run_program_argp_options[] = { + {"tl", 'T', "TIME_LIMIT", 0, "Set time limit (in second)", 1}, + {"rtl", 'R', "TIME_LIMIT", 0, "Set real time limit (in second)", 2}, + {"ml", 'M', "MEMORY_LIMIT", 0, "Set memory limit (in mb)", 3}, + {"ol", 'O', "OUTPUT_LIMIT", 0, "Set output limit (in mb)", 4}, + {"sl", 'S', "STACK_LIMIT", 0, "Set stack limit (in mb)", 5}, + {"in", 'i', "IN", 0, "Set input file name", 6}, + {"out", 'o', "OUT", 0, "Set output file name", 7}, + {"err", 'e', "ERR", 0, "Set error file name", 8}, + {"work-path", 'w', "WORK_PATH", 0, "Set the work path of the program", 9}, + {"type", 't', "TYPE", 0, "Set the program type (for some program such as python)", 10}, + {"res", 'r', "RESULT_FILE", 0, "Set the file name for outputing the result ", 10}, + {"add-readable", 500, "FILE", 0, "Add a readable file", 11}, + {"add-writable", 505, "FILE", 0, "Add a writable file", 11}, + {"unsafe", 501, 0, 0, "Don't check dangerous syscalls", 12}, + {"show-trace-details", 502, 0, 0, "Show trace details", 13}, + {"allow-proc", 503, 0, 0, "Allow fork, exec... etc.", 14}, + {"add-readable-raw", 504, "FILE", 0, "Add a readable (don't transform to its real path)", 15}, + {"add-writable-raw", 506, "FILE", 0, "Add a writable (don't transform to its real path)", 15}, + {0}}; +error_t run_program_argp_parse_opt(int key, char *arg, struct argp_state *state) { + runp::config *config = (runp::config *)state->input; + + switch (key) { + case 'T': + config->limits.time = round(std::stod(arg) * 1000) / 1000; + break; + case 'R': + config->limits.real_time = round(std::stod(arg) * 1000) / 1000; + break; + case 'M': + config->limits.memory = atoi(arg); + break; + case 'O': + config->limits.output = atoi(arg); + break; + case 'S': + config->limits.stack = atoi(arg); + break; + case 'i': + config->input_file_name = arg; + break; + case 'o': + config->output_file_name = arg; + break; + case 'e': + config->error_file_name = arg; + break; + case 'w': + config->work_path = arg; + break; + case 'r': + config->result_file_name = arg; + break; + case 't': + config->type = arg; + break; + case 500: + config->readable_file_names.push_back(realpath(arg)); + break; + case 501: + config->unsafe = true; + break; + case 502: + config->need_show_trace_details = true; + break; + case 503: + config->allow_proc = true; + break; + case 504: + config->readable_file_names.push_back(arg); + break; + case 505: + config->writable_file_names.push_back(realpath_for_write(arg)); + break; + case 506: + config->writable_file_names.push_back(arg); + break; + case ARGP_KEY_ARG: + config->program_name = arg; + for (int i = state->next; i < state->argc; i++) { + config->rest_args.push_back(state->argv[i]); + } + state->next = state->argc; + break; + case ARGP_KEY_END: + if (state->arg_num == 0) { + argp_usage(state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; } char run_program_argp_args_doc[] = "program arg1 arg2 ..."; char run_program_argp_doc[] = "run_program: a tool to run program safely"; -argp run_program_argp = { - run_program_argp_options, - run_program_argp_parse_opt, - run_program_argp_args_doc, - run_program_argp_doc -}; - -RunProgramConfig run_program_config; +argp run_program_argp = {run_program_argp_options, run_program_argp_parse_opt, + run_program_argp_args_doc, run_program_argp_doc}; void parse_args(int argc, char **argv) { - run_program_config.time_limit = 1; - run_program_config.real_time_limit = -1; - run_program_config.memory_limit = 256; - run_program_config.output_limit = 64; - run_program_config.stack_limit = 1024; - run_program_config.input_file_name = "stdin"; - run_program_config.output_file_name = "stdout"; - run_program_config.error_file_name = "stderr"; - run_program_config.work_path = ""; - run_program_config.result_file_name = "stdout"; - run_program_config.type = "default"; - run_program_config.safe_mode = true; - run_program_config.need_show_trace_details = false; - run_program_config.allow_proc = false; - - argp_parse(&run_program_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &run_program_config); - - if (run_program_config.real_time_limit == -1) - run_program_config.real_time_limit = run_program_config.time_limit + 2; - run_program_config.stack_limit = min(run_program_config.stack_limit, run_program_config.memory_limit); - - if (!run_program_config.work_path.empty()) { - if (chdir(run_program_config.work_path.c_str()) == -1) { - exit(put_result(run_program_config.result_file_name, RS_JGF)); - } - } - - if (run_program_config.type == "java8" || run_program_config.type == "java11") { - run_program_config.program_name = run_program_config.argv[0]; - } else { - run_program_config.program_name = realpath(run_program_config.argv[0]); - } - if (run_program_config.work_path.empty()) { - run_program_config.work_path = dirname(run_program_config.program_name); - run_program_config.program_basename = basename(run_program_config.program_name); - run_program_config.argv[0] = "./" + run_program_config.program_basename; - - if (chdir(run_program_config.work_path.c_str()) == -1) { - exit(put_result(run_program_config.result_file_name, RS_JGF)); - } - } - - if (run_program_config.type == "python2") { - string pre[4] = {"/usr/bin/python2", "-E", "-s", "-B"}; - run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 4); - } else if (run_program_config.type == "python3") { - string pre[3] = {"/usr/bin/python3", "-I", "-B"}; - run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 3); - } else if (run_program_config.type == "java8") { - string pre[3] = {"/usr/lib/jvm/java-8-openjdk-amd64/bin/java", "-Xmx1024m", "-Xss1024m"}; - run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 3); - } else if (run_program_config.type == "java11") { - string pre[3] = {"/usr/lib/jvm/java-11-openjdk-amd64/bin/java", "-Xmx1024m", "-Xss1024m"}; - run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 3); - } + run_program_config.limits.time = 1; + run_program_config.limits.real_time = -1; + run_program_config.limits.memory = 256; + run_program_config.limits.output = 64; + run_program_config.limits.stack = 1024; + run_program_config.input_file_name = "stdin"; + run_program_config.output_file_name = "stdout"; + run_program_config.error_file_name = "stderr"; + run_program_config.work_path = ""; + run_program_config.result_file_name = "stdout"; + run_program_config.type = "default"; + run_program_config.unsafe = false; + run_program_config.need_show_trace_details = false; + run_program_config.allow_proc = false; + + argp_parse(&run_program_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &run_program_config); + + runp::result::result_file_name = run_program_config.result_file_name; + + if (run_program_config.limits.real_time == -1) { + run_program_config.limits.real_time = run_program_config.limits.time + 2; + } + run_program_config.limits.stack = + std::min(run_program_config.limits.stack, run_program_config.limits.memory); + + // NOTE: program_name is the full path of the program, not just the file name (but can start + // with "./") + if (run_program_config.work_path.empty()) { + run_program_config.work_path = realpath(getcwd()); + if (!is_len_valid_path(run_program_config.work_path)) { + // work path does not exist + runp::result(runp::RS_JGF, "error code: WPDNE1").dump_and_exit(); + } + } else { + run_program_config.work_path = realpath(run_program_config.work_path); + if (!is_len_valid_path(run_program_config.work_path) + || chdir(run_program_config.work_path.c_str()) == -1) { + // work path does not exist + runp::result(runp::RS_JGF, "error code: WPDNE2").dump_and_exit(); + } + } + if (!is_len_valid_path(realpath(run_program_config.program_name))) { + // invalid program name + runp::result(runp::RS_JGF, "error code: INVPGN2").dump_and_exit(); + } + if (!available_program_type_set.count(run_program_config.type)) { + // invalid program type + runp::result(runp::RS_JGF, "error code: INVPGT").dump_and_exit(); + } + + try { + run_program_config.gen_full_args(); + } catch (std::exception &e) { + // fail to generate full args + runp::result(runp::RS_JGF, "error code: GFULARGS").dump_and_exit(); + } } -void set_limit(int r, int rcur, int rmax = -1) { - if (rmax == -1) - rmax = rcur; - struct rlimit l; - if (getrlimit(r, &l) == -1) { - exit(55); - } - l.rlim_cur = rcur; - l.rlim_max = rmax; - if (setrlimit(r, &l) == -1) { - exit(55); - } +void set_limit(int r, ssize_t rcur, ssize_t rmax = -1) { + if (rmax == -1) rmax = rcur; + struct rlimit l; + if (getrlimit(r, &l) == -1) { + exit(55); + } + l.rlim_cur = rcur; + l.rlim_max = rmax; + if (setrlimit(r, &l) == -1) { + exit(55); + } } -void run_child() { - set_limit(RLIMIT_CPU, run_program_config.time_limit, run_program_config.real_time_limit); - set_limit(RLIMIT_FSIZE, run_program_config.output_limit << 20); - set_limit(RLIMIT_STACK, run_program_config.stack_limit << 20); - - if (run_program_config.input_file_name != "stdin") { - if (freopen(run_program_config.input_file_name.c_str(), "r", stdin) == NULL) { - exit(11); - } - } - if (run_program_config.output_file_name != "stdout" && run_program_config.output_file_name != "stderr") { - if (freopen(run_program_config.output_file_name.c_str(), "w", stdout) == NULL) { - exit(12); - } - } - if (run_program_config.error_file_name != "stderr") { - if (run_program_config.error_file_name == "stdout") { - if (dup2(1, 2) == -1) { - exit(13); - } - } else { - if (freopen(run_program_config.error_file_name.c_str(), "w", stderr) == NULL) { - exit(14); - } - } - - if (run_program_config.output_file_name == "stderr") { - if (dup2(2, 1) == -1) { - exit(15); - } - } - } - - char *env_path_str = getenv("PATH"); - char *env_lang_str = getenv("LANG"); - char *env_shell_str = getenv("SHELL"); - string env_path = env_path_str ? env_path_str : ""; - string env_lang = env_lang_str ? env_lang_str : ""; - string env_shell = env_shell_str ? env_shell_str : ""; - - clearenv(); - setenv("USER", "poor_program", 1); - setenv("LOGNAME", "poor_program", 1); - setenv("HOME", run_program_config.work_path.c_str(), 1); - if (env_lang_str) { - setenv("LANG", env_lang.c_str(), 1); - } - if (env_path_str) { - setenv("PATH", env_path.c_str(), 1); - } - setenv("PWD", run_program_config.work_path.c_str(), 1); - if (env_shell_str) { - setenv("SHELL", env_shell.c_str(), 1); - } - - char **program_c_argv = new char*[run_program_config.argv.size() + 1]; - for (size_t i = 0; i < run_program_config.argv.size(); i++) { - program_c_argv[i] = new char[run_program_config.argv[i].size() + 1]; - strcpy(program_c_argv[i], run_program_config.argv[i].c_str()); - } - program_c_argv[run_program_config.argv.size()] = NULL; - - if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) { - exit(16); - } - if (execv(program_c_argv[0], program_c_argv) == -1) { - exit(17); - } + +void setup_watching_timer() { + itimerval val; + val.it_value = {0, 100'000}; // First tick after 100 ms + val.it_interval = {0, 100'000}; // Then every 100 ms + // Use ITIMER_PROF to count both user and kernel time. + setitimer(ITIMER_PROF, &val, NULL); } -const int MaxNRPChildren = 50; -struct rp_child_proc { - pid_t pid; - int mode; -}; -int n_rp_children; +[[noreturn]] void run_child() { + setpgid(0, 0); + + set_limit(RLIMIT_FSIZE, run_program_config.limits.output << 20ll); + set_limit(RLIMIT_STACK, run_program_config.limits.stack << 20ll); + set_limit(RLIMIT_AS, (run_program_config.limits.memory * 2ll + 64ll) << 20ll); + + if (run_program_config.input_file_name != "stdin") { + if (freopen(run_program_config.input_file_name.c_str(), "r", stdin) == NULL) { + exit(11); + } + } + if (run_program_config.output_file_name != "stdout" + && run_program_config.output_file_name != "stderr") { + if (freopen(run_program_config.output_file_name.c_str(), "w", stdout) == NULL) { + exit(12); + } + } + if (run_program_config.error_file_name != "stderr") { + if (run_program_config.error_file_name == "stdout") { + if (dup2(1, 2) == -1) { + exit(13); + } + } else { + if (freopen(run_program_config.error_file_name.c_str(), "w", stderr) == NULL) { + exit(14); + } + } + + if (run_program_config.output_file_name == "stderr") { + if (dup2(2, 1) == -1) { + exit(15); + } + } + } + + char *env_path_str = getenv("PATH"); + char *env_lang_str = getenv("LANG"); + char *env_shell_str = getenv("SHELL"); + std::string env_path = env_path_str ? env_path_str : ""; + std::string env_lang = env_lang_str ? env_lang_str : ""; + std::string env_shell = env_shell_str ? env_shell_str : ""; + + clearenv(); + setenv("USER", "poor_program", 1); + setenv("LOGNAME", "poor_program", 1); + setenv("HOME", run_program_config.work_path.c_str(), 1); + if (env_lang_str) { + setenv("LANG", env_lang.c_str(), 1); + } + if (env_path_str) { + setenv("PATH", env_path.c_str(), 1); + } + setenv("PWD", run_program_config.work_path.c_str(), 1); + if (env_shell_str) { + setenv("SHELL", env_shell.c_str(), 1); + } + + char **program_c_argv = new char *[run_program_config.full_args.size() + 1]; + for (size_t i = 0; i < run_program_config.full_args.size(); i++) { + program_c_argv[i] = run_program_config.full_args[i].data(); + } + program_c_argv[run_program_config.full_args.size()] = NULL; + + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) { + exit(16); + } + kill(getpid(), SIGSTOP); + if (!run_program_config.unsafe && !set_seccomp_bpf()) { + exit(99); + } + + pid_t pid = fork(); + if (pid == 0) { + setup_watching_timer(); + execv(program_c_argv[0], program_c_argv); + _exit(17); + } else if (pid != -1) { + int status; + while (wait(&status) > 0); + } + exit(17); +} + +// limit for the safe mode, an upper limit for the number of calls to fork/vfork/clone +const size_t MAX_TOTAL_RP_CHILDREN = 100; +size_t total_rp_children = 0; + +struct timeval start_time; +struct timeval end_time; pid_t rp_timer_pid; -rp_child_proc rp_children[MaxNRPChildren]; +std::vector rp_children; +struct rusage *ruse0p = NULL; + +bool has_real_TLE() { + struct timeval elapsed; + timersub(&end_time, &start_time, &elapsed); + return elapsed.tv_sec + elapsed.tv_usec / 1'000'000. >= run_program_config.limits.real_time; +} int rp_children_pos(pid_t pid) { - for (int i = 0; i < n_rp_children; i++) { - if (rp_children[i].pid == pid) { - return i; - } - } - return -1; + for (size_t i = 0; i < rp_children.size(); i++) { + if (rp_children[i].pid == pid) { + return (int)i; + } + } + return -1; } -int rp_children_add(pid_t pid) { - if (n_rp_children == MaxNRPChildren) { - return -1; - } - rp_children[n_rp_children].pid = pid; - rp_children[n_rp_children].mode = -1; - n_rp_children++; - return 0; +void rp_children_add(pid_t pid) { + rp_child_proc rpc; + rpc.pid = pid; + rpc.flags = CPF_STARTUP | CPF_IGNORE_ONE_SIGSTOP; + rp_children.push_back(rpc); } void rp_children_del(pid_t pid) { - int new_n = 0; - for (int i = 0; i < n_rp_children; i++) { - if (rp_children[i].pid != pid) { - rp_children[new_n++] = rp_children[i]; - } - } - n_rp_children = new_n; + size_t new_n = 0; + for (size_t i = 0; i < rp_children.size(); i++) { + if (rp_children[i].pid != pid) { + rp_children[new_n++] = rp_children[i]; + } + } + rp_children.resize(new_n); +} + +std::string get_usage_summary(struct rusage *rusep) { + struct timeval elapsed; + timersub(&end_time, &start_time, &elapsed); + + std::ostringstream sout; + struct timeval total_cpu; + timeradd(&rusep->ru_utime, &rusep->ru_stime, &total_cpu); + + sout << "[statistics]" << '\n'; + sout << "user CPU / total CPU / elapsed real time: "; + sout << rusep->ru_utime.tv_sec * 1000 + rusep->ru_utime.tv_usec / 1000 << "ms / "; + sout << total_cpu.tv_sec * 1000 + total_cpu.tv_usec / 1000 << "ms / "; + sout << elapsed.tv_sec * 1000 + elapsed.tv_usec / 1000 << "ms." << '\n'; + sout << "max RSS: " << rusep->ru_maxrss << "kb." << '\n'; + sout << "total number of threads: " << total_rp_children + 1 << "." << '\n'; + sout << "voluntary / total context switches: " << rusep->ru_nvcsw << " / " + << rusep->ru_nvcsw + rusep->ru_nivcsw << "."; + + return sout.str(); } void stop_child(pid_t pid) { - kill(pid, SIGKILL); + kill(pid, SIGKILL); } -void stop_all() { - kill(rp_timer_pid, SIGKILL); - for (int i = 0; i < n_rp_children; i++) { - kill(rp_children[i].pid, SIGKILL); - } + +void stop_all(runp::result res) { + struct rusage tmp, ruse, *rusep = ruse0p; + + kill(rp_timer_pid, SIGKILL); + killpg(rp_children[0].pid, SIGKILL); + + // in case some process changes its pgid + for (auto &rpc : rp_children) { + kill(rpc.pid, SIGKILL); + } + + int stat; + while (true) { + pid_t pid = wait4(-1, &stat, __WALL, &tmp); + // cerr << "stop_all: wait " << pid << endl; + if (pid < 0) { + if (errno == EINTR) { + continue; + } else if (errno == ECHILD) { + break; + } else { + res.dump_and_exit(); + } + } + + if (pid != rp_timer_pid && pid != rp_children[0].pid) { + if (res.type != runp::RS_AC) { + if (rp_children.size() >= 2 && pid == rp_children[1].pid) { + ruse = tmp; + rusep = &ruse; + } + } else if (rp_children.size() >= 2 && pid != rp_children[1].pid) { + res = runp::result(runp::RS_RE, "main thread exited before others"); + } + } + + // it is possible that a newly created process hasn't been logged into rp_children + // kill it for safty + kill(pid, SIGKILL); + } + + if (rusep) { + res.extra += "\n"; + res.extra += get_usage_summary(rusep); + } + + res.dump_and_exit(); } -RunResult trace_children() { - rp_timer_pid = fork(); - if (rp_timer_pid == -1) { - stop_all(); - return RunResult(RS_JGF); - } else if (rp_timer_pid == 0) { - struct timespec ts; - ts.tv_sec = run_program_config.real_time_limit; - ts.tv_nsec = 0; - nanosleep(&ts, NULL); - exit(0); - } - - if (run_program_config.need_show_trace_details) { - cerr << "timerpid " << rp_timer_pid << endl; - } - - pid_t prev_pid = -1; - while (true) { - int stat = 0; - int sig = 0; - struct rusage ruse; - - pid_t pid = wait4(-1, &stat, __WALL, &ruse); - if (run_program_config.need_show_trace_details) { - if (prev_pid != pid) { - cerr << "----------" << pid << "----------" << endl; - } - prev_pid = pid; - } - if (pid == rp_timer_pid) { - if (WIFEXITED(stat) || WIFSIGNALED(stat)) { - stop_all(); - return RunResult(RS_TLE); - } - continue; - } - - int p = rp_children_pos(pid); - if (p == -1) { - if (run_program_config.need_show_trace_details) { - fprintf(stderr, "new_proc %lld\n", (long long int)pid); - } - if (rp_children_add(pid) == -1) { - stop_child(pid); - stop_all(); - return RunResult(RS_DGS); - } - p = n_rp_children - 1; - } - - int usertim = ruse.ru_utime.tv_sec * 1000 + ruse.ru_utime.tv_usec / 1000; - int usermem = ruse.ru_maxrss; - if (usertim > run_program_config.time_limit * 1000) { - stop_all(); - return RunResult(RS_TLE); - } - if (usermem > run_program_config.memory_limit * 1024) { - stop_all(); - return RunResult(RS_MLE); - } - - if (WIFEXITED(stat)) { - if (run_program_config.need_show_trace_details) { - fprintf(stderr, "exit : %d\n", WEXITSTATUS(stat)); - } - if (rp_children[0].mode == -1) { - stop_all(); - return RunResult(RS_JGF, -1, -1, WEXITSTATUS(stat)); - } else { - if (pid == rp_children[0].pid) { - stop_all(); - return RunResult(RS_AC, usertim, usermem, WEXITSTATUS(stat)); - } else { - rp_children_del(pid); - continue; - } - } - } - - if (WIFSIGNALED(stat)) { - if (run_program_config.need_show_trace_details) { - fprintf(stderr, "sig exit : %d\n", WTERMSIG(stat)); - } - if (pid == rp_children[0].pid) { - switch(WTERMSIG(stat)) { - case SIGXCPU: // nearly impossible - stop_all(); - return RunResult(RS_TLE); - case SIGXFSZ: - stop_all(); - return RunResult(RS_OLE); - default: - stop_all(); - return RunResult(RS_RE); - } - } else { - rp_children_del(pid); - continue; - } - } - - if (WIFSTOPPED(stat)) { - sig = WSTOPSIG(stat); - - if (rp_children[p].mode == -1) { - if ((p == 0 && sig == SIGTRAP) || (p != 0 && sig == SIGSTOP)) { - if (p == 0) { - int ptrace_opt = PTRACE_O_EXITKILL | PTRACE_O_TRACESYSGOOD; - if (run_program_config.safe_mode) { - ptrace_opt |= PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK; - ptrace_opt |= PTRACE_O_TRACEEXEC; - } - if (ptrace(PTRACE_SETOPTIONS, pid, NULL, ptrace_opt) == -1) { - stop_all(); - return RunResult(RS_JGF); - } - } - sig = 0; - } - rp_children[p].mode = 0; - } else if (sig == (SIGTRAP | 0x80)) { - if (rp_children[p].mode == 0) { - if (run_program_config.safe_mode) { - if (!check_safe_syscall(pid, run_program_config.need_show_trace_details)) { - stop_all(); - return RunResult(RS_DGS); - } - } - rp_children[p].mode = 1; - } else { - if (run_program_config.safe_mode) { - on_syscall_exit(pid, run_program_config.need_show_trace_details); - } - rp_children[p].mode = 0; - } - - sig = 0; - } else if (sig == SIGTRAP) { - switch ((stat >> 16) & 0xffff) { - case PTRACE_EVENT_CLONE: - case PTRACE_EVENT_FORK: - case PTRACE_EVENT_VFORK: - sig = 0; - break; - case PTRACE_EVENT_EXEC: - rp_children[p].mode = 1; - sig = 0; - break; - case 0: - break; - default: - stop_all(); - return RunResult(RS_JGF); - } - } - - if (sig != 0) { - if (run_program_config.need_show_trace_details) { - fprintf(stderr, "sig : %d\n", sig); - } - } - - switch(sig) { - case SIGXCPU: - stop_all(); - return RunResult(RS_TLE); - case SIGXFSZ: - stop_all(); - return RunResult(RS_OLE); - } - } - - ptrace(PTRACE_SYSCALL, pid, NULL, sig); - } +run_event next_event() { + static struct rusage ruse; + static pid_t prev_pid = -1; + run_event e; + + int stat = 0; + + e.pid = wait4(-1, &stat, __WALL, &ruse); + const int wait_errno = errno; + gettimeofday(&end_time, NULL); + + ruse0p = NULL; + if (e.pid < 0) { + if (wait_errno == EINTR) { + e.type = ET_SKIP; + return e; + } + stop_all(runp::result(runp::RS_JGF, "error code: WT4FAL")); // wait4 failed + } + + if (run_program_config.need_show_trace_details) { + if (prev_pid != e.pid) { + std::cerr << "----------" << e.pid << "----------\n"; + } + prev_pid = e.pid; + } + + if (e.pid == rp_timer_pid) { + e.type = WIFEXITED(stat) || WIFSIGNALED(stat) ? ET_REAL_TLE : ET_SKIP; + return e; + } + + if (has_real_TLE()) { + e.type = ET_REAL_TLE; + return e; + } + + int p = rp_children_pos(e.pid); + if (p == -1) { + if (run_program_config.need_show_trace_details) { + fprintf(stderr, "new_proc %lld\n", (long long int)e.pid); + } + rp_children_add(e.pid); + p = (int)rp_children.size() - 1; + } + + e.cp = rp_children.data() + p; + ruse0p = p == 1 ? &ruse : NULL; + + if (p >= 1) { + e.usertim = ruse.ru_utime.tv_sec * 1000 + ruse.ru_utime.tv_usec / 1000; + e.usermem = ruse.ru_maxrss; + if (e.usertim > run_program_config.limits.time * 1000) { + e.type = ET_USER_CPU_TLE; + return e; + } + if (e.usermem > run_program_config.limits.memory * 1024) { + e.type = ET_MLE; + return e; + } + } + + if (WIFEXITED(stat)) { + if (p == 0) { + stop_all(runp::result( + runp::RS_JGF, "error code: ZROEX")); // the 0th child process exited unexpectedly + } + e.type = ET_EXIT; + e.exitcode = WEXITSTATUS(stat); + return e; + } + + if (WIFSIGNALED(stat)) { + if (p == 0) { + stop_all( + runp::result(runp::RS_JGF, + "error code: ZROSIG")); // the 0th child process signaled unexpectedly + } + e.type = ET_SIGNALED; + e.sig = WTERMSIG(stat); + return e; + } + + if (!WIFSTOPPED(stat)) { + stop_all( + runp::result(runp::RS_JGF, "error code: NSTOP")); // expected WIFSTOPPED, but it is not + } + + e.sig = WSTOPSIG(stat); + e.pevent = (unsigned)stat >> 16; + + if (run_program_config.need_show_trace_details) { + fprintf(stderr, "sig : %s\n", strsignal(e.sig)); + } + + if (e.cp->flags & CPF_STARTUP) { + int ptrace_opt = PTRACE_O_EXITKILL; + if (p == 0 || !run_program_config.unsafe) { + ptrace_opt |= PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK; + } + if (!run_program_config.unsafe) { + ptrace_opt |= PTRACE_O_TRACESECCOMP; + } + if (ptrace(PTRACE_SETOPTIONS, e.pid, NULL, ptrace_opt) == -1) { + stop_all(runp::result(runp::RS_JGF, "error code: PTRCFAL")); // ptrace failed + } + e.cp->flags &= ~CPF_STARTUP; + } + + switch (e.sig) { + case SIGTRAP: + switch (e.pevent) { + case 0: + case PTRACE_EVENT_CLONE: + case PTRACE_EVENT_FORK: + case PTRACE_EVENT_VFORK: + e.sig = 0; + e.type = ET_RESTART; + return e; + case PTRACE_EVENT_SECCOMP: + e.sig = 0; + e.type = ET_SECCOMP_STOP; + return e; + default: + stop_all(runp::result(runp::RS_JGF, + "error code: PTRCSIG")); // unknown ptrace signal + } + case SIGSTOP: + if (e.cp->flags & CPF_IGNORE_ONE_SIGSTOP) { + e.sig = 0; + e.type = ET_RESTART; + e.cp->flags &= ~CPF_IGNORE_ONE_SIGSTOP; + } else { + e.type = ET_SIGNAL_DELIVERY_STOP; + } + return e; + case SIGPROF: + // This signal is sent by ITIMER_PROF periodically to wake up the tracer + // for resource usage checks. The signal is not delivered to the child. + e.sig = 0; + e.type = ET_RESTART; + return e; + case SIGXFSZ: + e.type = ET_OLE; + return e; + default: + e.type = ET_SIGNAL_DELIVERY_STOP; + return e; + } } -RunResult run_parent(pid_t pid) { - init_conf(run_program_config); - - n_rp_children = 0; +void dispatch_event(run_event &&e) { + auto restart_op = PTRACE_CONT; + + switch (e.type) { + case ET_SKIP: + return; + case ET_REAL_TLE: + stop_all(runp::result(runp::RS_TLE, + "elapsed real time limit exceeded: >" + + std::to_string(run_program_config.limits.real_time) + "s")); + case ET_USER_CPU_TLE: + stop_all(runp::result(runp::RS_TLE, "user CPU time limit exceeded: >" + + std::to_string(run_program_config.limits.time) + + "s")); + case ET_MLE: + stop_all(runp::result( + runp::RS_MLE, + "max RSS >" + std::to_string(run_program_config.limits.memory) + "MB")); + case ET_OLE: + stop_all(runp::result(runp::RS_OLE, + "output limit exceeded: >" + + std::to_string(run_program_config.limits.output) + "MB")); + case ET_EXIT: + if (run_program_config.need_show_trace_details) { + fprintf(stderr, "exit : %d\n", e.exitcode); + } + if (rp_children[0].flags & CPF_STARTUP) { + stop_all( + runp::result(runp::RS_JGF, "error code: CPCMDER1")); // rp_children mode error + } else if (rp_children.size() < 2 || (rp_children[1].flags & CPF_STARTUP)) { + stop_all( + runp::result(runp::RS_JGF, "error code: CPCMDER2")); // rp_children mode error + } else { + if (e.cp == rp_children.data() + 1) { + stop_all(runp::result(runp::RS_AC, + "exit with code " + std::to_string(e.exitcode), e.usertim, + e.usermem, e.exitcode)); + } else { + rp_children_del(e.pid); + } + } + return; + + case ET_SIGNALED: + if (run_program_config.need_show_trace_details) { + fprintf(stderr, "sig exit : %s\n", strsignal(e.sig)); + } + if (e.cp == rp_children.data() + 1) { + stop_all(runp::result( + runp::RS_RE, std::string("process terminated by signal: ") + strsignal(e.sig))); + } else { + rp_children_del(e.pid); + } + return; + + case ET_SECCOMP_STOP: + if (e.cp != rp_children.data() + 0 && !run_program_config.unsafe) { + if (!e.cp->check_safe_syscall()) { + if (e.cp->suspicious) { + stop_all(runp::result(runp::RS_DGS, e.cp->error)); + } else { + stop_all(runp::result(runp::RS_RE, e.cp->error)); + } + } + if (e.cp->try_to_create_new_process) { + total_rp_children++; + if (total_rp_children > MAX_TOTAL_RP_CHILDREN) { + stop_all( + runp::result(runp::RS_DGS, + "the limit on the amount of child processes is exceeded")); + } + } + } + break; + + case ET_SIGNAL_DELIVERY_STOP: + break; + + case ET_RESTART: + break; + } + + if (ptrace(restart_op, e.pid, NULL, e.sig) < 0) { + if (errno != ESRCH) { + stop_all(runp::result(runp::RS_JGF, "error code: PTRESFAL")); // ptrace restart failed + } + } +} - rp_children_add(pid); - return trace_children(); +[[noreturn]] void trace_children() { + rp_timer_pid = fork(); + if (rp_timer_pid == -1) { + runp::result(runp::RS_JGF, "error code: FKFAL2").dump_and_exit(); // fork failed + } else if (rp_timer_pid == 0) { + struct timespec ts = runp::double_to_timespec(run_program_config.limits.real_time); + ts.tv_nsec += 100'000'000; + if (ts.tv_nsec >= 1'000'000'000) { + ts.tv_sec += 1; + ts.tv_nsec -= 1'000'000'000; + } + nanosleep(&ts, NULL); + exit(0); + } + + if (run_program_config.need_show_trace_details) { + std::cerr << "timerpid " << rp_timer_pid << '\n'; + } + + while (true) { + dispatch_event(next_event()); + } } + int main(int argc, char **argv) { - self_path[readlink("/proc/self/exe", self_path, PATH_MAX)] = '\0'; - parse_args(argc, argv); - - pid_t pid = fork(); - if (pid == -1) { - return put_result(run_program_config.result_file_name, RS_JGF); - } else if (pid == 0) { - run_child(); - } else { - return put_result(run_program_config.result_file_name, run_parent(pid)); - } - return put_result(run_program_config.result_file_name, RS_JGF); + try { + fs::path self_path = fs::read_symlink("/proc/self/exe"); + runp::run_path = self_path.parent_path(); + } catch (std::exception &e) { + runp::result(runp::RS_JGF, "error code: PTHFAL2").dump_and_exit(); // path failed + } + + parse_args(argc, argv); + init_conf(); + + gettimeofday(&start_time, NULL); + pid_t pid = fork(); + if (pid == -1) { + runp::result(runp::RS_JGF, "error code: FKFAL2").dump_and_exit(); // fork failed + } else if (pid == 0) { + run_child(); + } else { + rp_children_add(pid); + trace_children(); + } } diff --git a/judger/uoj_judger/run/run_program_conf.h b/judger/uoj_judger/run/run_program_conf.h deleted file mode 100644 index bf1e32cac..000000000 --- a/judger/uoj_judger/run/run_program_conf.h +++ /dev/null @@ -1,788 +0,0 @@ -#ifdef __x86_64__ -typedef unsigned long long int reg_val_t; -#define REG_SYSCALL orig_rax -#define REG_RET rax -#define REG_ARG0 rdi -#define REG_ARG1 rsi -#define REG_ARG2 rdx -#define REG_ARG3 rcx -#else -typedef long int reg_val_t; -#define REG_SYSCALL orig_eax -#define REG_RET eax -#define REG_ARG0 ebx -#define REG_ARG1 ecx -#define REG_ARG2 edx -#define REG_ARG3 esx -#endif - -const size_t MaxPathLen = 200; - -set writable_file_name_set; -set readable_file_name_set; -set statable_file_name_set; -set soft_ban_file_name_set; -int syscall_max_cnt[1000]; -bool syscall_should_soft_ban[1000]; - -string basename(const string &path) { - size_t p = path.rfind('/'); - if (p == string::npos) { - return path; - } else { - return path.substr(p + 1); - } -} -string dirname(const string &path) { - size_t p = path.rfind('/'); - if (p == string::npos) { - return ""; - } else { - return path.substr(0, p); - } -} -string getcwdp(pid_t pid) { - char s[20]; - char cwd[MaxPathLen + 1]; - if (pid != 0) { - sprintf(s, "/proc/%lld/cwd", (long long int)pid); - } else { - sprintf(s, "/proc/self/cwd"); - } - int l = readlink(s, cwd, MaxPathLen); - if (l == -1) { - return ""; - } - cwd[l] = '\0'; - return cwd; -} -string abspath(pid_t pid, const string &path) { - if (path.size() > MaxPathLen) { - return ""; - } - if (path.empty()) { - return path; - } - string s; - string b; - size_t st; - if (path[0] == '/') { - s = "/"; - st = 1; - } else { - s = getcwdp(pid) + "/"; - st = 0; - } - for (size_t i = st; i < path.size(); i++) { - b += path[i]; - if (path[i] == '/') { - if (b == "../" && !s.empty()) { - if (s == "./") { - s = "../"; - } else if (s != "/") { - size_t p = s.size() - 1; - while (p > 0 && s[p - 1] != '/') { - p--; - } - if (s.size() - p == 3 && s[p] == '.' && s[p + 1] == '.' && s[p + 2] == '/') { - s += b; - } else { - s.resize(p); - } - } - } else if (b != "./" && b != "/") { - s += b; - } - b.clear(); - } - } - if (b == ".." && !s.empty()) { - if (s == "./") { - s = ".."; - } else if (s != "/") { - size_t p = s.size() - 1; - while (p > 0 && s[p - 1] != '/') { - p--; - } - if (s.size() - p == 3 && s[p] == '.' && s[p + 1] == '.' && s[p + 2] == '/') { - s += b; - } else { - s.resize(p); - } - } - } else if (b != ".") { - s += b; - } - if (s.size() >= 2 && s[s.size() - 1] == '/') { - s.resize(s.size() - 1); - } - return s; -} -string realpath(const string &path) { - char real[PATH_MAX + 1] = {}; - if (realpath(path.c_str(), real) == NULL) { - return ""; - } - return real; -} - -inline bool is_in_set_smart(string name, const set &s) { - if (name.size() > MaxPathLen) { - return false; - } - if (s.count(name)) { - return true; - } - for (size_t i = 0; i + 1 < name.size(); i++) { - if ((i == 0 || name[i - 1] == '/') && name[i] == '.' && name[i + 1] == '.' && (i + 2 == name.size() || name[i + 2] == '.')) { - return false; - } - } - int level; - for (level = 0; !name.empty(); name = dirname(name), level++) { - if (level == 1 && s.count(name + "/*")) { - return true; - } - if (s.count(name + "/")) { - return true; - } - } - if (level == 1 && s.count("/*")) { - return true; - } - if (s.count("/")) { - return true; - } - return false; -} - -inline bool is_writable_file(string name) { - if (name == "/") { - return writable_file_name_set.count("system_root"); - } - return is_in_set_smart(name, writable_file_name_set) || is_in_set_smart(realpath(name), writable_file_name_set); -} -inline bool is_readable_file(const string &name) { - if (is_writable_file(name)) { - return true; - } - if (name == "/") { - return readable_file_name_set.count("system_root"); - } - return is_in_set_smart(name, readable_file_name_set) || is_in_set_smart(realpath(name), readable_file_name_set); -} -inline bool is_statable_file(const string &name) { - if (is_readable_file(name)) { - return true; - } - if (name == "/") { - return statable_file_name_set.count("system_root"); - } - return is_in_set_smart(name, statable_file_name_set) || is_in_set_smart(realpath(name), statable_file_name_set); -} -inline bool is_soft_ban_file(const string &name) { - if (name == "/") { - return soft_ban_file_name_set.count("system_root"); - } - return is_in_set_smart(name, soft_ban_file_name_set) || is_in_set_smart(realpath(name), soft_ban_file_name_set); -} - -#ifdef __x86_64__ -int syscall_max_cnt_list_default[][2] = { - {__NR_read , -1}, - {__NR_write , -1}, - {__NR_readv , -1}, - {__NR_writev , -1}, - {__NR_pread64 , -1}, - {__NR_open , -1}, - {__NR_unlink , -1}, - {__NR_close , -1}, - {__NR_readlink , -1}, - {__NR_openat , -1}, - {__NR_unlinkat , -1}, - {__NR_readlinkat , -1}, - {__NR_stat , -1}, - {__NR_fstat , -1}, - {__NR_lstat , -1}, - {__NR_lseek , -1}, - {__NR_access , -1}, - {__NR_dup , -1}, - {__NR_dup2 , -1}, - {__NR_dup3 , -1}, - {__NR_ioctl , -1}, - {__NR_fcntl , -1}, - - {__NR_mmap , -1}, - {__NR_mprotect , -1}, - {__NR_munmap , -1}, - {__NR_brk , -1}, - {__NR_mremap , -1}, - {__NR_msync , -1}, - {__NR_mincore , -1}, - {__NR_madvise , -1}, - - {__NR_rt_sigaction , -1}, - {__NR_rt_sigprocmask, -1}, - {__NR_rt_sigreturn , -1}, - {__NR_rt_sigpending , -1}, - {__NR_sigaltstack , -1}, - - {__NR_getcwd , -1}, - - {__NR_exit , -1}, - {__NR_exit_group , -1}, - - {__NR_arch_prctl , -1}, - - {__NR_gettimeofday , -1}, - {__NR_getrlimit , -1}, - {__NR_getrusage , -1}, - {__NR_times , -1}, - {__NR_time , -1}, - {__NR_clock_gettime , -1}, - - {__NR_restart_syscall, -1}, - - {-1 , -1} -}; - -int syscall_soft_ban_list_default[] = { - -1 -}; - -const char *readable_file_name_list_default[] = { - "/etc/ld.so.nohwcap", - "/etc/ld.so.preload", - "/etc/ld.so.cache", - "/lib/x86_64-linux-gnu/", - "/usr/lib/x86_64-linux-gnu/", - "/usr/lib/locale/locale-archive", - "/proc/self/exe", - "/etc/timezone", - "/usr/share/zoneinfo/", - "/dev/random", - "/dev/urandom", - "/proc/meminfo", - "/etc/localtime", - NULL -}; - -#else -#error T_T -#endif - -void add_file_permission(const string &file_name, char mode) { - if (mode == 'w') { - writable_file_name_set.insert(file_name); - } else if (mode == 'r') { - readable_file_name_set.insert(file_name); - } else if (mode == 's') { - statable_file_name_set.insert(file_name); - } - for (string name = dirname(file_name); !name.empty(); name = dirname(name)) { - statable_file_name_set.insert(name); - } -} - -void init_conf(const RunProgramConfig &config) { - for (int i = 0; syscall_max_cnt_list_default[i][0] != -1; i++) { - syscall_max_cnt[syscall_max_cnt_list_default[i][0]] = syscall_max_cnt_list_default[i][1]; - } - for (int i = 0; syscall_soft_ban_list_default[i] != -1; i++) { - syscall_should_soft_ban[syscall_soft_ban_list_default[i]] = true; - } - - for (int i = 0; readable_file_name_list_default[i]; i++) { - readable_file_name_set.insert(readable_file_name_list_default[i]); - } - statable_file_name_set.insert(config.work_path + "/"); - - if (config.type != "java8" && config.type != "java11") { - add_file_permission(config.program_name, 'r'); - } else { - int p = config.program_name.find('.'); - if (p == string::npos) { - readable_file_name_set.insert(config.work_path + "/"); - } else { - readable_file_name_set.insert(config.work_path + "/" + config.program_name.substr(0, p) + "/"); - } - } - add_file_permission(config.work_path, 'r'); - - for (vector::const_iterator it = config.extra_readable_files.begin(); it != config.extra_readable_files.end(); it++) { - add_file_permission(*it, 'r'); - } - for (vector::const_iterator it = config.extra_writable_files.begin(); it != config.extra_writable_files.end(); it++) { - add_file_permission(*it, 'w'); - } - - writable_file_name_set.insert("/dev/null"); - - if (config.allow_proc) { - syscall_max_cnt[__NR_clone ] = -1; - syscall_max_cnt[__NR_fork ] = -1; - syscall_max_cnt[__NR_vfork ] = -1; - syscall_max_cnt[__NR_nanosleep ] = -1; - syscall_max_cnt[__NR_execve ] = -1; - } - - if (config.type == "python2") { - syscall_max_cnt[__NR_set_tid_address] = 1; - syscall_max_cnt[__NR_set_robust_list] = 1; - syscall_max_cnt[__NR_futex ] = -1; - - syscall_max_cnt[__NR_getdents ] = -1; - syscall_max_cnt[__NR_getdents64 ] = -1; - - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - syscall_max_cnt[__NR_prlimit64 ] = -1; - syscall_max_cnt[__NR_getpid ] = -1; - syscall_max_cnt[__NR_sysinfo ] = -1; - # endif - - readable_file_name_set.insert("/usr/bin/python2.7"); - readable_file_name_set.insert("/usr/lib/python2.7/"); - readable_file_name_set.insert("/usr/bin/lib/python2.7/"); - readable_file_name_set.insert("/usr/local/lib/python2.7/"); - readable_file_name_set.insert("/usr/lib/pymodules/python2.7/"); - readable_file_name_set.insert("/usr/bin/Modules/"); - readable_file_name_set.insert("/usr/bin/pybuilddir.txt"); - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - readable_file_name_set.insert("/usr/lib/locale/"); - readable_file_name_set.insert(config.work_path + "/answer.code"); - # endif - - statable_file_name_set.insert("/usr"); - statable_file_name_set.insert("/usr/bin"); - } else if (config.type == "python3") { - syscall_max_cnt[__NR_set_tid_address] = 1; - syscall_max_cnt[__NR_set_robust_list] = 1; - syscall_max_cnt[__NR_futex ] = -1; - - syscall_max_cnt[__NR_getdents ] = -1; - syscall_max_cnt[__NR_getdents64 ] = -1; - - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - syscall_max_cnt[__NR_prlimit64 ] = -1; - syscall_max_cnt[__NR_getrandom ] = -1; - syscall_max_cnt[__NR_sysinfo ] = -1; - syscall_max_cnt[__NR_getpid ] = -1; - # endif - - readable_file_name_set.insert("/usr/bin/python3"); - readable_file_name_set.insert("/usr/lib/python3/"); - # ifdef UOJ_JUDGER_PYTHON3_VERSION - readable_file_name_set.insert("/usr/bin/python" UOJ_JUDGER_PYTHON3_VERSION); - readable_file_name_set.insert("/usr/lib/python" UOJ_JUDGER_PYTHON3_VERSION "/"); - readable_file_name_set.insert("/usr/bin/lib/python" UOJ_JUDGER_PYTHON3_VERSION "/"); - readable_file_name_set.insert("/usr/local/lib/python" UOJ_JUDGER_PYTHON3_VERSION "/"); - # else - readable_file_name_set.insert("/usr/bin/python3.4"); - readable_file_name_set.insert("/usr/lib/python3.4/"); - readable_file_name_set.insert("/usr/bin/lib/python3.4/"); - readable_file_name_set.insert("/usr/local/lib/python3.4/"); - # endif - readable_file_name_set.insert("/usr/bin/pyvenv.cfg"); - readable_file_name_set.insert("/usr/pyvenv.cfg"); - readable_file_name_set.insert("/usr/bin/Modules/"); - readable_file_name_set.insert("/usr/bin/pybuilddir.txt"); - readable_file_name_set.insert("/usr/lib/dist-python"); - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - readable_file_name_set.insert("/usr/lib/locale/"); - readable_file_name_set.insert(config.work_path + "/answer.code"); - # endif - - statable_file_name_set.insert("/usr"); - statable_file_name_set.insert("/usr/bin"); - statable_file_name_set.insert("/usr/lib"); - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - statable_file_name_set.insert("/usr/lib/python38.zip"); - # endif - } else if (config.type == "java8") { - syscall_max_cnt[__NR_gettid ] = -1; - syscall_max_cnt[__NR_set_tid_address] = 1; - syscall_max_cnt[__NR_set_robust_list] = 14; - syscall_max_cnt[__NR_futex ] = -1; - - syscall_max_cnt[__NR_uname ] = 1; - - syscall_max_cnt[__NR_clone ] = 13; - - syscall_max_cnt[__NR_getdents ] = 4; - syscall_max_cnt[__NR_getdents64 ] = 4; - - syscall_max_cnt[__NR_clock_getres ] = 2; - - syscall_max_cnt[__NR_setrlimit ] = 1; - - syscall_max_cnt[__NR_sched_getaffinity] = -1; - syscall_max_cnt[__NR_sched_yield ] = -1; - - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - syscall_max_cnt[__NR_prlimit64 ] = -1; - syscall_max_cnt[__NR_getpid ] = -1; - syscall_max_cnt[__NR_sysinfo ] = -1; - syscall_max_cnt[__NR_clone ] = -1; - syscall_max_cnt[__NR_set_robust_list] = -1; - syscall_max_cnt[__NR_prctl ] = -1; - # endif - - syscall_should_soft_ban[__NR_socket ] = true; - syscall_should_soft_ban[__NR_connect ] = true; - syscall_should_soft_ban[__NR_geteuid ] = true; - syscall_should_soft_ban[__NR_getuid ] = true; - - soft_ban_file_name_set.insert("/etc/nsswitch.conf"); - soft_ban_file_name_set.insert("/etc/passwd"); - - add_file_permission("/usr/lib/jvm/java-8-openjdk-amd64/", 'r'); - readable_file_name_set.insert("/sys/devices/system/cpu/"); - readable_file_name_set.insert("/proc/"); - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - readable_file_name_set.insert("/sys/fs/cgroup/cpu/"); - readable_file_name_set.insert("/sys/fs/cgroup/cpu,cpuacct/"); - readable_file_name_set.insert("/sys/fs/cgroup/memory/"); - readable_file_name_set.insert("/usr/lib/locale/"); - # endif - statable_file_name_set.insert("/usr/java/"); - statable_file_name_set.insert("/tmp/"); - } else if (config.type == "java11") { - syscall_max_cnt[__NR_gettid ] = -1; - syscall_max_cnt[__NR_set_tid_address] = 1; - syscall_max_cnt[__NR_set_robust_list] = 15; - syscall_max_cnt[__NR_futex ] = -1; - - syscall_max_cnt[__NR_uname ] = 1; - - syscall_max_cnt[__NR_clone ] = 14; - - syscall_max_cnt[__NR_getdents ] = 4; - syscall_max_cnt[__NR_getdents64 ] = 4; - - syscall_max_cnt[__NR_clock_getres ] = 2; - - syscall_max_cnt[__NR_setrlimit ] = 1; - - syscall_max_cnt[__NR_sched_getaffinity] = -1; - syscall_max_cnt[__NR_sched_yield ] = -1; - - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - syscall_max_cnt[__NR_prlimit64 ] = -1; - syscall_max_cnt[__NR_getpid ] = -1; - syscall_max_cnt[__NR_sysinfo ] = -1; - syscall_max_cnt[__NR_clone ] = -1; - syscall_max_cnt[__NR_set_robust_list] = -1; - syscall_max_cnt[__NR_uname ] = -1; - syscall_max_cnt[__NR_clock_getres ] = -1; - syscall_max_cnt[__NR_pread64 ] = -1; - syscall_max_cnt[__NR_prctl ] = -1; - syscall_max_cnt[__NR_nanosleep ] = -1; - syscall_max_cnt[__NR_clock_nanosleep] = -1; - # endif - - syscall_should_soft_ban[__NR_socket ] = true; - syscall_should_soft_ban[__NR_connect ] = true; - syscall_should_soft_ban[__NR_geteuid ] = true; - syscall_should_soft_ban[__NR_getuid ] = true; - - soft_ban_file_name_set.insert("/etc/nsswitch.conf"); - soft_ban_file_name_set.insert("/etc/passwd"); - - add_file_permission("/usr/lib/jvm/java-11-openjdk-amd64/", 'r'); - readable_file_name_set.insert("/sys/devices/system/cpu/"); - readable_file_name_set.insert("/proc/"); - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - readable_file_name_set.insert("/sys/fs/cgroup/cpu/"); - readable_file_name_set.insert("/sys/fs/cgroup/cpu,cpuacct/"); - readable_file_name_set.insert("/sys/fs/cgroup/memory/"); - readable_file_name_set.insert("/usr/share/java/"); - readable_file_name_set.insert("/usr/lib/locale/"); - readable_file_name_set.insert("/etc/oracle/java/usagetracker.properties"); - # endif - statable_file_name_set.insert("/usr/java/"); - statable_file_name_set.insert("/tmp/"); - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - statable_file_name_set.insert("/usr/share/"); - # endif - } else if (config.type == "compiler") { - syscall_max_cnt[__NR_gettid ] = -1; - syscall_max_cnt[__NR_set_tid_address] = -1; - syscall_max_cnt[__NR_set_robust_list] = -1; - syscall_max_cnt[__NR_futex ] = -1; - - syscall_max_cnt[__NR_getpid ] = -1; - syscall_max_cnt[__NR_vfork ] = -1; - syscall_max_cnt[__NR_fork ] = -1; - syscall_max_cnt[__NR_clone ] = -1; - syscall_max_cnt[__NR_execve ] = -1; - syscall_max_cnt[__NR_wait4 ] = -1; - - syscall_max_cnt[__NR_clock_gettime ] = -1; - syscall_max_cnt[__NR_clock_getres ] = -1; - - syscall_max_cnt[__NR_setrlimit ] = -1; - syscall_max_cnt[__NR_pipe ] = -1; - syscall_max_cnt[__NR_pipe2 ] = -1; - - syscall_max_cnt[__NR_getdents64 ] = -1; - syscall_max_cnt[__NR_getdents ] = -1; - - syscall_max_cnt[__NR_umask ] = -1; - syscall_max_cnt[__NR_rename ] = -1; - syscall_max_cnt[__NR_chmod ] = -1; - syscall_max_cnt[__NR_mkdir ] = -1; - - syscall_max_cnt[__NR_chdir ] = -1; - syscall_max_cnt[__NR_fchdir ] = -1; - - syscall_max_cnt[__NR_ftruncate ] = -1; // for javac = = - - syscall_max_cnt[__NR_sched_getaffinity] = -1; // for javac = = - syscall_max_cnt[__NR_sched_yield ] = -1; // for javac = = - - syscall_max_cnt[__NR_uname ] = -1; // for javac = = - syscall_max_cnt[__NR_sysinfo ] = -1; // for javac = = - - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - syscall_max_cnt[__NR_prlimit64 ] = -1; - syscall_max_cnt[__NR_getrandom ] = -1; - syscall_max_cnt[__NR_pread64 ] = -1; - syscall_max_cnt[__NR_prctl ] = -1; - syscall_max_cnt[__NR_nanosleep ] = -1; - syscall_max_cnt[__NR_clock_nanosleep] = -1; - syscall_max_cnt[__NR_socketpair ] = -1; - # endif - - syscall_should_soft_ban[__NR_socket ] = true; // for javac - syscall_should_soft_ban[__NR_connect ] = true; // for javac - syscall_should_soft_ban[__NR_geteuid ] = true; // for javac - syscall_should_soft_ban[__NR_getuid ] = true; // for javac - - writable_file_name_set.insert("/tmp/"); - - readable_file_name_set.insert(config.work_path); - writable_file_name_set.insert(config.work_path + "/"); - - readable_file_name_set.insert(abspath(0, string(self_path) + "/../runtime") + "/"); - # ifdef UOJ_JUDGER_BASESYSTEM_UBUNTU1804 - readable_file_name_set.insert("/sys/fs/cgroup/cpu/"); - readable_file_name_set.insert("/sys/fs/cgroup/cpu,cpuacct/"); - readable_file_name_set.insert("/sys/fs/cgroup/memory/"); - readable_file_name_set.insert("/etc/oracle/java/usagetracker.properties"); - # endif - - readable_file_name_set.insert("system_root"); - readable_file_name_set.insert("/usr/"); - readable_file_name_set.insert("/lib/"); - readable_file_name_set.insert("/lib64/"); - readable_file_name_set.insert("/bin/"); - readable_file_name_set.insert("/sbin/"); - // readable_file_name_set.insert("/proc/meminfo"); - // readable_file_name_set.insert("/proc/self/"); - - readable_file_name_set.insert("/sys/devices/system/cpu/"); - readable_file_name_set.insert("/proc/"); - soft_ban_file_name_set.insert("/etc/nsswitch.conf"); // for javac = = - soft_ban_file_name_set.insert("/etc/passwd"); // for javac = = - - readable_file_name_set.insert("/etc/timezone"); - # ifdef UOJ_JUDGER_FPC_VERSION - readable_file_name_set.insert("/etc/fpc-" UOJ_JUDGER_FPC_VERSION ".cfg.d/"); - # else - readable_file_name_set.insert("/etc/fpc-2.6.2.cfg.d/"); - # endif - readable_file_name_set.insert("/etc/fpc.cfg"); - - statable_file_name_set.insert("/*"); - } -} - -string read_string_from_regs(reg_val_t addr, pid_t pid) { - char res[MaxPathLen + 1], *ptr = res; - while (ptr != res + MaxPathLen) { - *(reg_val_t*)ptr = ptrace(PTRACE_PEEKDATA, pid, addr, NULL); - for (int i = 0; i < sizeof(reg_val_t); i++, ptr++, addr++) { - if (*ptr == 0) { - return res; - } - } - } - res[MaxPathLen] = 0; - return res; -} -string read_abspath_from_regs(reg_val_t addr, pid_t pid) { - return abspath(pid, read_string_from_regs(addr, pid)); -} - -inline void soft_ban_syscall(pid_t pid, user_regs_struct reg) { - reg.REG_SYSCALL += 1024; - ptrace(PTRACE_SETREGS, pid, NULL, ®); -} - -inline bool on_dgs_file_detect(pid_t pid, user_regs_struct reg, const string &fn) { - if (is_soft_ban_file(fn)) { - soft_ban_syscall(pid, reg); - return true; - } else { - return false; - } -} - -inline bool check_safe_syscall(pid_t pid, bool need_show_trace_details) { - struct user_regs_struct reg; - ptrace(PTRACE_GETREGS, pid, NULL, ®); - - int cur_instruction = ptrace(PTRACE_PEEKTEXT, pid, reg.rip - 2, NULL) & 0xffff; - if (cur_instruction != 0x050f) { - if (need_show_trace_details) { - fprintf(stderr, "informal syscall %d\n", cur_instruction); - } - return false; - } - - int syscall = (int)reg.REG_SYSCALL; - if (0 > syscall || syscall >= 1000) { - return false; - } - if (need_show_trace_details) { - fprintf(stderr, "syscall %d\n", (int)syscall); - } - - if (syscall_should_soft_ban[syscall]) { - soft_ban_syscall(pid, reg); - } else if (syscall_max_cnt[syscall]-- == 0) { - if (need_show_trace_details) { - fprintf(stderr, "dgs %d\n", (int)syscall); - } - return false; - } - - if (syscall == __NR_open || syscall == __NR_openat) { - reg_val_t fn_addr; - reg_val_t flags; - if (syscall == __NR_open) { - fn_addr = reg.REG_ARG0; - flags = reg.REG_ARG1; - } else { - fn_addr = reg.REG_ARG1; - flags = reg.REG_ARG2; - } - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "open "); - - switch (flags & O_ACCMODE) { - case O_RDONLY: - fprintf(stderr, "r "); - break; - case O_WRONLY: - fprintf(stderr, "w "); - break; - case O_RDWR: - fprintf(stderr, "rw"); - break; - default: - fprintf(stderr, "??"); - break; - } - fprintf(stderr, " %s\n", fn.c_str()); - } - - bool is_read_only = (flags & O_ACCMODE) == O_RDONLY && - (flags & O_CREAT) == 0 && - (flags & O_EXCL) == 0 && - (flags & O_TRUNC) == 0; - if (is_read_only) { - if (realpath(fn) != "" && !is_readable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else { - if (!is_writable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } - } else if (syscall == __NR_readlink || syscall == __NR_readlinkat) { - reg_val_t fn_addr; - if (syscall == __NR_readlink) { - fn_addr = reg.REG_ARG0; - } else { - fn_addr = reg.REG_ARG1; - } - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "readlink %s\n", fn.c_str()); - } - if (!is_readable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else if (syscall == __NR_unlink || syscall == __NR_unlinkat) { - reg_val_t fn_addr; - if (syscall == __NR_unlink) { - fn_addr = reg.REG_ARG0; - } else { - fn_addr = reg.REG_ARG1; - } - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "unlink %s\n", fn.c_str()); - } - if (!is_writable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else if (syscall == __NR_access) { - reg_val_t fn_addr = reg.REG_ARG0; - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "access %s\n", fn.c_str()); - } - if (!is_statable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else if (syscall == __NR_stat || syscall == __NR_lstat) { - reg_val_t fn_addr = reg.REG_ARG0; - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "stat %s\n", fn.c_str()); - } - if (!is_statable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else if (syscall == __NR_execve) { - reg_val_t fn_addr = reg.REG_ARG0; - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "execve %s\n", fn.c_str()); - } - if (!is_readable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } else if (syscall == __NR_chmod || syscall == __NR_rename) { - reg_val_t fn_addr = reg.REG_ARG0; - string fn = read_abspath_from_regs(fn_addr, pid); - if (need_show_trace_details) { - fprintf(stderr, "change %s\n", fn.c_str()); - } - if (!is_writable_file(fn)) { - return on_dgs_file_detect(pid, reg, fn); - } - } - return true; -} - -inline void on_syscall_exit(pid_t pid, bool need_show_trace_details) { - struct user_regs_struct reg; - ptrace(PTRACE_GETREGS, pid, NULL, ®); - if (need_show_trace_details) { - if ((long long int)reg.REG_SYSCALL >= 1024) { - fprintf(stderr, "ban sys %lld\n", (long long int)reg.REG_SYSCALL - 1024); - } else { - fprintf(stderr, "exitsys %lld (ret %d)\n", (long long int)reg.REG_SYSCALL, (int)reg.REG_RET); - } - } - - if ((long long int)reg.REG_SYSCALL >= 1024) { - reg.REG_SYSCALL -= 1024; - reg.REG_RET = -EACCES; - ptrace(PTRACE_SETREGS, pid, NULL, ®); - } -} diff --git a/judger/uoj_judger/run/run_program_sandbox.h b/judger/uoj_judger/run/run_program_sandbox.h new file mode 100644 index 000000000..5313a04db --- /dev/null +++ b/judger/uoj_judger/run/run_program_sandbox.h @@ -0,0 +1,1632 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uoj_run.h" + +enum EX_CHECK_TYPE : unsigned { + ECT_NONE = 0, + ECT_CNT = 1, + ECT_FILE_OP = 1 << 1, // it is a file operation + ECT_END_AT = 1 << 2, // this file operation ends with "at" (e.g., openat) + ECT_FILEAT_OP = ECT_FILE_OP | ECT_END_AT, // it is a file operation ended with "at" + ECT_FILE_W = 1 << 3, // intend to write + ECT_FILE_R = 1 << 4, // intend to read + ECT_FILE_S = 1 << 5, // intend to stat + ECT_CHECK_OPEN_FLAGS = + 1 << 6, // check flags to determine whether it is to read/write (for open and openat) + ECT_FILE2_W = 1 << 7, // intend to write (2nd file) + ECT_FILE2_R = 1 << 8, // intend to read (2nd file) + ECT_FILE2_S = 1 << 9, // intend to stat (2nd file) + ECT_CLONE_THREAD = 1 << 10, // for clone(). Check that clone is making a non-suspicious thread + ECT_KILL_SIG0_ALLOWED = 1 << 11, // forbid kill but killing with sig0 is allowed +}; + +struct syscall_info { + EX_CHECK_TYPE extra_check; + int max_cnt; + bool should_soft_ban = false; + bool is_kill = false; + + syscall_info() : extra_check(ECT_CNT), max_cnt(0) {} + syscall_info(unsigned extra_check, int max_cnt) : + extra_check((EX_CHECK_TYPE)extra_check), max_cnt(max_cnt) {} + + static syscall_info unlimited() { + return syscall_info(ECT_NONE, -1); + } + + static syscall_info count_based(int max_cnt) { + return syscall_info(ECT_CNT, max_cnt); + } + + static syscall_info with_extra_check(unsigned extra_check, int max_cnt = -1) { + if (max_cnt != -1) { + extra_check |= ECT_CNT; + } + return syscall_info(extra_check, max_cnt); + } + + static syscall_info kill_type_syscall(unsigned extra_check = ECT_CNT, int max_cnt = 0) { + if (max_cnt != -1) { + extra_check |= ECT_CNT; + } + syscall_info res(extra_check, max_cnt); + res.is_kill = true; + return res; + } + + static syscall_info soft_ban() { + syscall_info res(ECT_CNT, 0); + res.should_soft_ban = true; + return res; + } +}; + +#ifndef __x86_64__ +#error only x86-64 is supported! +#endif + +/* + * a mask that tells seccomp that it should SCMP_ACT_ERRNO(no) + * when syscall #(mask | no) is called + * used to implement SCMP_ACT_ERRNO(no) using ptrace: + * std::set the syscall number to mask | no; + * PTRACE_CONT + * seccomp performs SCMP_ACT_ERRNO(no) + */ +const int SYSCALL_SOFT_BAN_MASK = 996 << 18; + +std::vector supported_soft_ban_errno_list = { + ENOENT, // No such file or directory + EPERM, // Operation not permitted + EACCES, // Permission denied +}; + +std::set available_program_type_set = {"default", "python2", "python3", "java", + "compiler"}; + +/* + * folder program: the program to run is a folder, not a single regular file + */ +std::set folder_program_type_set = {"java"}; + +std::map>> allowed_syscall_list = { + {"default", + { + {__NR_read, syscall_info::unlimited()}, + {__NR_pread64, syscall_info::unlimited()}, + {__NR_write, syscall_info::unlimited()}, + {__NR_pwrite64, syscall_info::unlimited()}, + {__NR_readv, syscall_info::unlimited()}, + {__NR_writev, syscall_info::unlimited()}, + {__NR_preadv, syscall_info::unlimited()}, + {__NR_pwritev, syscall_info::unlimited()}, + {__NR_sendfile, syscall_info::unlimited()}, + + {__NR_close, syscall_info::unlimited()}, + {__NR_fstat, syscall_info::unlimited()}, + {__NR_fstatfs, syscall_info::unlimited()}, + {__NR_lseek, syscall_info::unlimited()}, + {__NR_dup, syscall_info::unlimited()}, + {__NR_dup2, syscall_info::unlimited()}, + {__NR_dup3, syscall_info::unlimited()}, + {__NR_ioctl, syscall_info::unlimited()}, + {__NR_fcntl, syscall_info::unlimited()}, + + {__NR_gettid, syscall_info::unlimited()}, + {__NR_getpid, syscall_info::unlimited()}, + + {__NR_mmap, syscall_info::unlimited()}, + {__NR_mprotect, syscall_info::unlimited()}, + {__NR_munmap, syscall_info::unlimited()}, + {__NR_brk, syscall_info::unlimited()}, + {__NR_mremap, syscall_info::unlimited()}, + {__NR_msync, syscall_info::unlimited()}, + {__NR_mincore, syscall_info::unlimited()}, + {__NR_madvise, syscall_info::unlimited()}, + + {__NR_rt_sigaction, syscall_info::unlimited()}, + {__NR_rt_sigprocmask, syscall_info::unlimited()}, + {__NR_rt_sigreturn, syscall_info::unlimited()}, + {__NR_rt_sigpending, syscall_info::unlimited()}, + {__NR_sigaltstack, syscall_info::unlimited()}, + + {__NR_getcwd, syscall_info::unlimited()}, + {__NR_uname, syscall_info::unlimited()}, + + {__NR_exit, syscall_info::unlimited()}, + {__NR_exit_group, syscall_info::unlimited()}, + + {__NR_arch_prctl, syscall_info::unlimited()}, + + {__NR_getrusage, syscall_info::unlimited()}, + {__NR_getrlimit, syscall_info::unlimited()}, + + {__NR_gettimeofday, syscall_info::unlimited()}, + {__NR_times, syscall_info::unlimited()}, + {__NR_time, syscall_info::unlimited()}, + {__NR_clock_gettime, syscall_info::unlimited()}, + {__NR_clock_getres, syscall_info::unlimited()}, + + {__NR_restart_syscall, syscall_info::unlimited()}, + + // for startup + {__NR_setitimer, syscall_info::count_based(1)}, + {__NR_execve, syscall_info::count_based(1)}, + {__NR_set_robust_list, syscall_info::unlimited()}, + + {__NR_set_tid_address, syscall_info::count_based(1)}, + {__NR_rseq, syscall_info::count_based(1)}, + + // need to check file permissions + {__NR_open, syscall_info::with_extra_check(ECT_FILE_OP | ECT_CHECK_OPEN_FLAGS)}, + {__NR_openat, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_CHECK_OPEN_FLAGS)}, + {__NR_readlink, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)}, + {__NR_readlinkat, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_S)}, + {__NR_access, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_R)}, + {__NR_faccessat, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_R)}, + {__NR_faccessat2, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_R)}, + {__NR_stat, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)}, + {__NR_statfs, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)}, + {__NR_lstat, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)}, + {__NR_newfstatat, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_S)}, + {__NR_statx, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_S)}, + + // kill could be DGS or RE + {__NR_kill, syscall_info::kill_type_syscall()}, + {__NR_tkill, syscall_info::kill_type_syscall()}, + {__NR_tgkill, syscall_info::kill_type_syscall()}, + + // for python + {__NR_prlimit64, syscall_info::soft_ban()}, + + // for python and java + {__NR_sysinfo, syscall_info::unlimited()}, + + // python3 uses this call to generate random numbers + // for fairness, all types of programs can use this call + {__NR_getrandom, syscall_info::unlimited()}, + + // futex + {__NR_futex, syscall_info::unlimited()}, + + // some python library uses epoll (e.g., z3-solver) + {__NR_epoll_create, syscall_info::unlimited()}, + {__NR_epoll_create1, syscall_info::unlimited()}, + {__NR_epoll_ctl, syscall_info::unlimited()}, + {__NR_epoll_wait, syscall_info::unlimited()}, + {__NR_epoll_pwait, syscall_info::unlimited()}, + + // for java + {__NR_geteuid, syscall_info::unlimited()}, + {__NR_getuid, syscall_info::unlimited()}, + {__NR_setrlimit, syscall_info::soft_ban()}, + {__NR_socket, syscall_info::soft_ban()}, + {__NR_connect, syscall_info::soft_ban()}, + }}, + + {"allow_proc", + { + {__NR_clone, syscall_info::unlimited()}, + {__NR_clone3, syscall_info::unlimited()}, + {__NR_fork, syscall_info::unlimited()}, + {__NR_vfork, syscall_info::unlimited()}, + {__NR_nanosleep, syscall_info::unlimited()}, + {__NR_clock_nanosleep, syscall_info::unlimited()}, + {__NR_wait4, syscall_info::unlimited()}, + + {__NR_execve, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_R)}, + }}, + + {"python2", + { + {__NR_getdents, syscall_info::unlimited()}, + {__NR_getdents64, syscall_info::unlimited()}, + }}, + + {"python3", + { + {__NR_getdents, syscall_info::unlimited()}, + {__NR_getdents64, syscall_info::unlimited()}, + }}, + + {"java", + { + {__NR_clone, syscall_info::with_extra_check(ECT_CLONE_THREAD, 16)}, + {__NR_clone3, syscall_info::with_extra_check(ECT_CLONE_THREAD, 16)}, + {__NR_rseq, syscall_info::unlimited()}, + {__NR_prctl, syscall_info::unlimited()}, // TODO: add extra checks for prctl + {__NR_prlimit64, syscall_info::unlimited()}, // TODO: add extra checks for + // prlimit64 + + {__NR_getdents, syscall_info::unlimited()}, + {__NR_getdents64, syscall_info::unlimited()}, + + {__NR_sched_getaffinity, syscall_info::unlimited()}, + {__NR_sched_yield, syscall_info::unlimited()}, + + {__NR_nanosleep, syscall_info::unlimited()}, + {__NR_clock_nanosleep, syscall_info::unlimited()}, + }}, + + {"compiler", + { + {__NR_set_tid_address, syscall_info::unlimited()}, + {__NR_rseq, syscall_info::unlimited()}, + + {__NR_clone, syscall_info::unlimited()}, + {__NR_clone3, syscall_info::unlimited()}, + {__NR_fork, syscall_info::unlimited()}, + {__NR_vfork, syscall_info::unlimited()}, + {__NR_nanosleep, syscall_info::unlimited()}, + {__NR_clock_nanosleep, syscall_info::unlimited()}, + {__NR_wait4, syscall_info::unlimited()}, + + {__NR_geteuid, syscall_info::unlimited()}, + {__NR_getuid, syscall_info::unlimited()}, + {__NR_getgid, syscall_info::unlimited()}, + {__NR_getegid, syscall_info::unlimited()}, + {__NR_getppid, syscall_info::unlimited()}, + {__NR_setresuid, syscall_info::unlimited()}, + {__NR_setresgid, syscall_info::unlimited()}, + + {__NR_setrlimit, syscall_info::unlimited()}, + {__NR_prlimit64, syscall_info::unlimited()}, + {__NR_prctl, syscall_info::unlimited()}, + + {__NR_pipe, syscall_info::unlimited()}, + {__NR_pipe2, syscall_info::unlimited()}, + + // for java... we have no choice + {__NR_socketpair, syscall_info::unlimited()}, + {__NR_socket, syscall_info::unlimited()}, + {__NR_getsockname, syscall_info::unlimited()}, + {__NR_setsockopt, syscall_info::unlimited()}, + {__NR_connect, syscall_info::unlimited()}, + {__NR_sendto, syscall_info::unlimited()}, + {__NR_poll, syscall_info::unlimited()}, + {__NR_recvmsg, syscall_info::unlimited()}, + {__NR_sysinfo, syscall_info::unlimited()}, + + {__NR_umask, syscall_info::unlimited()}, + {__NR_getdents, syscall_info::unlimited()}, + {__NR_getdents64, syscall_info::unlimited()}, + + {__NR_chdir, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)}, + {__NR_fchdir, syscall_info::unlimited()}, + + {__NR_execve, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_R)}, + {__NR_execveat, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_R)}, + + {__NR_truncate, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W)}, + {__NR_ftruncate, syscall_info::unlimited()}, + + {__NR_chmod, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W)}, + {__NR_fchmodat, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_W)}, + {__NR_fchmod, syscall_info::unlimited()}, + + {__NR_rename, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W | ECT_FILE2_W)}, + {__NR_renameat, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_W | ECT_FILE2_W)}, + {__NR_renameat2, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_W | ECT_FILE2_W)}, + + {__NR_unlink, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W)}, + {__NR_unlinkat, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_W)}, + + {__NR_mkdir, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W)}, + {__NR_mkdirat, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_W)}, + + {__NR_rmdir, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_W)}, + + {__NR_fadvise64, syscall_info::unlimited()}, + + {__NR_sched_getaffinity, syscall_info::unlimited()}, + {__NR_sched_yield, syscall_info::unlimited()}, + + {__NR_kill, syscall_info::kill_type_syscall(ECT_KILL_SIG0_ALLOWED, -1)}, + {__NR_tkill, syscall_info::kill_type_syscall(ECT_KILL_SIG0_ALLOWED, -1)}, + {__NR_tgkill, syscall_info::kill_type_syscall(ECT_KILL_SIG0_ALLOWED, -1)}, + }}, +}; + +std::map> soft_ban_file_name_list = { + {"default", + { + "/dev/tty", + "/dev/pts/", + + // for java and javac... + "/etc/nsswitch.conf", + "/etc/passwd", + }}}; + +std::map> statable_file_name_list = { + {"default", {}}, + + {"python2", + { + "/usr/", + "/usr/bin/", + "/usr/lib/", + }}, + + {"python3", + { + "/usr/", + "/usr/bin/", + "/usr/lib/", + "/etc/python" UOJ_PYTHON3_VERSION "/", + }}, + + {"java", + { + "system_root", + "/tmp/", + }}, + + {"compiler", + { + "/*", + "/boot/", + }}, +}; + +std::map> readable_file_name_list = { + {"default", + { + "/lib/x86_64-linux-gnu/", "/usr/lib/x86_64-linux-gnu/", "/usr/lib/locale/", + "/usr/share/zoneinfo/", "/etc/ld.so.nohwcap", "/etc/ld.so.preload", "/etc/ld.so.cache", + "/etc/timezone", "/etc/localtime", "/etc/locale.alias", "/proc/self/", "/proc/*", + "/dev/random", "/dev/urandom", + "/sys/devices/system/cpu/", // for java & some python libraries + "/proc/sys/vm/", // for java + }}, + + {"python2", + { + "/etc/python2.7/", + "/usr/bin/python2.7", + "/usr/lib/python2.7/", + "/usr/bin/lib/python2.7/", + "/usr/local/lib/python2.7/", + "/usr/lib/pymodules/python2.7/", + "/usr/bin/Modules/", + "/usr/bin/pybuilddir.txt", + }}, + + {"python3", + { + "/etc/python/" UOJ_PYTHON3_VERSION, + "/usr/bin/python" UOJ_PYTHON3_VERSION, + "/usr/lib/python" UOJ_PYTHON3_VERSION "/", + "/usr/lib/python3/dist-packages/", + "/usr/bin/lib/python" UOJ_PYTHON3_VERSION "/", + "/usr/local/lib/python" UOJ_PYTHON3_VERSION "/", + "/usr/bin/pyvenv.cfg", + "/usr/pyvenv.cfg", + "/usr/bin/Modules/", + "/usr/bin/pybuilddir.txt", + "/usr/lib/dist-python", + }}, + + {"java", + { + UOJ_JDK "/", + "/sys/fs/cgroup/", + "/etc/java-" UOJ_JAVA_VERSION "-openjdk/", + "/usr/share/java/", + "/sys/kernel/mm/hugepages/", + "/sys/kernel/mm/transparent_hugepage/", + }}, + + {"compiler", + { + "system_root", + "/dev/", + "/usr/", + "/lib/", + "/lib64/", + "/bin/", + "/sbin/", + "/sys/fs/cgroup/", + "/proc/", + "/etc/timezone", + "/etc/alternatives/", + "/sys/kernel/mm/hugepages/", // java + "/sys/kernel/mm/transparent_hugepage/", // java + "/etc/python2.7/", + "/etc/python" UOJ_PYTHON3_VERSION "/", + "/etc/fpc.cfg", + "/etc/fpc-" UOJ_FPC_VERSION ".cfg", + "/etc/java-" UOJ_JAVA_VERSION "-openjdk/", + }}}; + +std::map> writable_file_name_list = { + {"default", + { + "/dev/null", + + // for java + "/proc/self/coredump_filter", + }}, + + {"compiler", + { + "/tmp/", + }}}; + +const int N_SYSCALL = 440; +std::string syscall_name[N_SYSCALL] = { + "read", + "write", + "open", + "close", + "stat", + "fstat", + "lstat", + "poll", + "lseek", + "mmap", + "mprotect", + "munmap", + "brk", + "rt_sigaction", + "rt_sigprocmask", + "rt_sigreturn", + "ioctl", + "pread64", + "pwrite64", + "readv", + "writev", + "access", + "pipe", + "select", + "sched_yield", + "mremap", + "msync", + "mincore", + "madvise", + "shmget", + "shmat", + "shmctl", + "dup", + "dup2", + "pause", + "nanosleep", + "getitimer", + "alarm", + "setitimer", + "getpid", + "sendfile", + "socket", + "connect", + "accept", + "sendto", + "recvfrom", + "sendmsg", + "recvmsg", + "shutdown", + "bind", + "listen", + "getsockname", + "getpeername", + "socketpair", + "setsockopt", + "getsockopt", + "clone", + "fork", + "vfork", + "execve", + "exit", + "wait4", + "kill", + "uname", + "semget", + "semop", + "semctl", + "shmdt", + "msgget", + "msgsnd", + "msgrcv", + "msgctl", + "fcntl", + "flock", + "fsync", + "fdatasync", + "truncate", + "ftruncate", + "getdents", + "getcwd", + "chdir", + "fchdir", + "rename", + "mkdir", + "rmdir", + "creat", + "link", + "unlink", + "symlink", + "readlink", + "chmod", + "fchmod", + "chown", + "fchown", + "lchown", + "umask", + "gettimeofday", + "getrlimit", + "getrusage", + "sysinfo", + "times", + "ptrace", + "getuid", + "syslog", + "getgid", + "setuid", + "setgid", + "geteuid", + "getegid", + "setpgid", + "getppid", + "getpgrp", + "setsid", + "setreuid", + "setregid", + "getgroups", + "setgroups", + "setresuid", + "getresuid", + "setresgid", + "getresgid", + "getpgid", + "setfsuid", + "setfsgid", + "getsid", + "capget", + "capset", + "rt_sigpending", + "rt_sigtimedwait", + "rt_sigqueueinfo", + "rt_sigsuspend", + "sigaltstack", + "utime", + "mknod", + "uselib", + "personality", + "ustat", + "statfs", + "fstatfs", + "sysfs", + "getpriority", + "setpriority", + "sched_setparam", + "sched_getparam", + "sched_setscheduler", + "sched_getscheduler", + "sched_get_priority_max", + "sched_get_priority_min", + "sched_rr_get_interval", + "mlock", + "munlock", + "mlockall", + "munlockall", + "vhangup", + "modify_ldt", + "pivot_root", + "_sysctl", + "prctl", + "arch_prctl", + "adjtimex", + "setrlimit", + "chroot", + "sync", + "acct", + "settimeofday", + "mount", + "umount2", + "swapon", + "swapoff", + "reboot", + "sethostname", + "setdomainname", + "iopl", + "ioperm", + "create_module", + "init_module", + "delete_module", + "get_kernel_syms", + "query_module", + "quotactl", + "nfsservctl", + "getpmsg", + "putpmsg", + "afs_syscall", + "tuxcall", + "security", + "gettid", + "readahead", + "setxattr", + "lsetxattr", + "fsetxattr", + "getxattr", + "lgetxattr", + "fgetxattr", + "listxattr", + "llistxattr", + "flistxattr", + "removexattr", + "lremovexattr", + "fremovexattr", + "tkill", + "time", + "futex", + "sched_setaffinity", + "sched_getaffinity", + "set_thread_area", + "io_setup", + "io_destroy", + "io_getevents", + "io_submit", + "io_cancel", + "get_thread_area", + "lookup_dcookie", + "epoll_create", + "epoll_ctl_old", + "epoll_wait_old", + "remap_file_pages", + "getdents64", + "set_tid_address", + "restart_syscall", + "semtimedop", + "fadvise64", + "timer_create", + "timer_settime", + "timer_gettime", + "timer_getoverrun", + "timer_delete", + "clock_settime", + "clock_gettime", + "clock_getres", + "clock_nanosleep", + "exit_group", + "epoll_wait", + "epoll_ctl", + "tgkill", + "utimes", + "vserver", + "mbind", + "set_mempolicy", + "get_mempolicy", + "mq_open", + "mq_unlink", + "mq_timedsend", + "mq_timedreceive", + "mq_notify", + "mq_getsetattr", + "kexec_load", + "waitid", + "add_key", + "request_key", + "keyctl", + "ioprio_set", + "ioprio_get", + "inotify_init", + "inotify_add_watch", + "inotify_rm_watch", + "migrate_pages", + "openat", + "mkdirat", + "mknodat", + "fchownat", + "futimesat", + "newfstatat", + "unlinkat", + "renameat", + "linkat", + "symlinkat", + "readlinkat", + "fchmodat", + "faccessat", + "pselect6", + "ppoll", + "unshare", + "set_robust_list", + "get_robust_list", + "splice", + "tee", + "sync_file_range", + "vmsplice", + "move_pages", + "utimensat", + "epoll_pwait", + "signalfd", + "timerfd_create", + "eventfd", + "fallocate", + "timerfd_settime", + "timerfd_gettime", + "accept4", + "signalfd4", + "eventfd2", + "epoll_create1", + "dup3", + "pipe2", + "inotify_init1", + "preadv", + "pwritev", + "rt_tgsigqueueinfo", + "perf_event_open", + "recvmmsg", + "fanotify_init", + "fanotify_mark", + "prlimit64", + "name_to_handle_at", + "open_by_handle_at", + "clock_adjtime", + "syncfs", + "sendmmsg", + "setns", + "getcpu", + "process_vm_readv", + "process_vm_writev", + "kcmp", + "finit_module", + "sched_setattr", + "sched_getattr", + "renameat2", + "seccomp", + "getrandom", + "memfd_create", + "kexec_file_load", + "bpf", + "execveat", + "userfaultfd", + "membarrier", + "mlock2", + "copy_file_range", + "preadv2", + "pwritev2", + "pkey_mprotect", + "pkey_alloc", + "pkey_free", + "statx", + "io_pgetevents", + "rseq", // 334 + "?335", + "?336", + "?337", + "?338", + "?339", + "?340", + "?341", + "?342", + "?343", + "?344", + "?345", + "?346", + "?347", + "?348", + "?349", + "?350", + "?351", + "?352", + "?353", + "?354", + "?355", + "?356", + "?357", + "?358", + "?359", + "?360", + "?361", + "?362", + "?363", + "?364", + "?365", + "?366", + "?367", + "?368", + "?369", + "?370", + "?371", + "?372", + "?373", + "?374", + "?375", + "?376", + "?377", + "?378", + "?379", + "?380", + "?381", + "?382", + "?383", + "?384", + "?385", + "?386", + "?387", + "?388", + "?389", + "?390", + "?391", + "?392", + "?393", + "?394", + "?395", + "?396", + "?397", + "?398", + "?399", + "?400", + "?401", + "?402", + "?403", + "?404", + "?405", + "?406", + "?407", + "?408", + "?409", + "?410", + "?411", + "?412", + "?413", + "?414", + "?415", + "?416", + "?417", + "?418", + "?419", + "?420", + "?421", + "?422", + "?423", + "?424", + "?425", + "?426", + "?427", + "?428", + "?429", + "?430", + "?431", + "?432", + "?433", + "?434", + "clone3", + "?436", + "?437", + "?438", + "faccessat2", // 439 +}; + +typedef unsigned long long int reg_val_t; +#define REG_SYSCALL orig_rax +#define REG_RET rax +#define REG_ARG0 rdi +#define REG_ARG1 rsi +#define REG_ARG2 rdx +#define REG_ARG3 rcx + +enum CHILD_PROC_FLAG : unsigned { CPF_STARTUP = 1u << 0, CPF_IGNORE_ONE_SIGSTOP = 1u << 2 }; + +struct rp_child_proc { + pid_t pid; + + unsigned flags; + + struct user_regs_struct reg = {}; + int syscall = -1; + std::string error; + bool suspicious = false; + bool try_to_create_new_process = false; + + void set_error_for_suspicious(const std::string &error); + void set_error_for_kill(); + void soft_ban_syscall(int set_no); + bool check_safe_syscall(); + bool check_file_permission(const std::string &op, const std::string &fn, char mode); +}; + +struct clone_args { + std::uint64_t flags; /* Flags bit mask */ + std::uint64_t pidfd; /* Where to store PID file descriptor (int *) */ + std::uint64_t child_tid; /* Where to store child TID, in child's memory (pid_t *) */ + std::uint64_t parent_tid; /* Where to store child TID, in parent's memory (pid_t *) */ + std::uint64_t exit_signal; /* Signal to deliver to parent on child termination */ + std::uint64_t stack; /* Pointer to lowest byte of stack */ + std::uint64_t stack_size; /* Size of stack */ + std::uint64_t tls; /* Location of new TLS */ + std::uint64_t set_tid; /* Pointer to a pid_t array (since Linux 5.5) */ + std::uint64_t set_tid_size; /* Number of elements in set_tid (since Linux 5.5) */ + std::uint64_t cgroup; /* File descriptor for target cgroup of child (since Linux 5.7) */ +}; + +void read_memory_from_addr(reg_val_t addr, pid_t pid, void *buf, size_t size) { + uint8_t *ptr = (uint8_t *)buf; + for (size_t i = 0; i < size; i += sizeof(reg_val_t)) { + reg_val_t data = ptrace(PTRACE_PEEKDATA, pid, addr + i, NULL); + size_t copy_size = std::min(sizeof(reg_val_t), size - i); + memcpy(ptr + i, &data, copy_size); + } +} + +const size_t MAX_PATH_LEN = 512; +const uint64_t MAX_FD_ID = 1 << 20; + +const std::string INVALID_PATH(PATH_MAX + 8, 'X'); +const std::string EMPTY_PATH_AFTER_FD = "?empty_path_after_fd"; + +runp::config run_program_config; + +std::set writable_file_name_set; +std::set readable_file_name_set; +std::set statable_file_name_set; +std::set soft_ban_file_name_set; + +syscall_info syscall_info_set[N_SYSCALL]; + +pid_t get_tgid_from_pid(pid_t pid) { + std::ifstream fin("/proc/" + std::to_string(pid) + "/status"); + std::string key; + while (fin >> key) { + if (key == "Tgid:") { + pid_t tgid; + if (fin >> tgid) { + return tgid; + } else { + return -1; + } + } + } + return -1; +} + +bool is_len_valid_path(const std::string &path) { + return !path.empty() && path.size() <= MAX_PATH_LEN; +} + +std::string path_or_len_invalid(const std::string &path) { + return is_len_valid_path(path) ? path : INVALID_PATH; +} + +std::string basename(const std::string &path) { + if (!is_len_valid_path(path)) { + return INVALID_PATH; + } + size_t p = path.rfind('/'); + if (p == std::string::npos) { + return path; + } else { + return path.substr(p + 1); // can be empty, e.g., path = "abc/" + } +} +std::string dirname(const std::string &path) { + if (!is_len_valid_path(path)) { + return INVALID_PATH; + } + size_t p = path.rfind('/'); + if (p == std::string::npos) { + return INVALID_PATH; + } else { + return path.substr(0, p); // can be empty, e.g., path = "/abc" + } +} +std::string realpath(const std::string &path) { + if (!is_len_valid_path(path)) { + return INVALID_PATH; + } + static char real[PATH_MAX + 1] = {}; + if (realpath(path.c_str(), real) == NULL) { + return INVALID_PATH; + } + return path_or_len_invalid(real); +} +std::string realpath_for_write(const std::string &path) { + std::string real = realpath(path); + if (!is_len_valid_path(path)) { + return INVALID_PATH; + } + + std::string b = basename(path); + if (!is_len_valid_path(b) || b == "." || b == "..") { + return INVALID_PATH; + } + real = realpath(dirname(path)); + if (!is_len_valid_path(real)) { + return INVALID_PATH; + } + return path_or_len_invalid(real + "/" + b); +} +std::string readlink(const std::string &path) { + if (!is_len_valid_path(path)) { + return INVALID_PATH; + } + static char buf[MAX_PATH_LEN + 1]; + ssize_t n = readlink(path.c_str(), buf, MAX_PATH_LEN + 1); + if (n > (ssize_t)MAX_PATH_LEN) { + return INVALID_PATH; + } else { + buf[n] = '\0'; + return path_or_len_invalid(buf); + } +} +std::string getcwd() { + char cwd[MAX_PATH_LEN + 1]; + if (getcwd(cwd, MAX_PATH_LEN) == NULL) { + return INVALID_PATH; + } else { + return path_or_len_invalid(cwd); + } +} +std::string getcwdp(pid_t pid) { + return realpath("/proc/" + (pid == 0 ? "self" : std::to_string(pid)) + "/cwd"); +} +std::string abspath(const std::string &path, pid_t pid, int fd = AT_FDCWD) { + static int depth = 0; + if (depth == 10 || !is_len_valid_path(path)) { + return INVALID_PATH; + } + + std::vector lv; + for (std::string cur = path; is_len_valid_path(cur); cur = dirname(cur)) { + lv.push_back(basename(cur)); + } + std::reverse(lv.begin(), lv.end()); + + std::string pos; + if (path[0] == '/') { + pos = "/"; + } else if (fd == AT_FDCWD) { + pos = getcwdp(pid); + } else { + depth++; + pos = abspath("/proc/self/fd/" + std::to_string(fd), pid); + depth--; + } + if (!is_len_valid_path(pos)) { + return INVALID_PATH; + } + + struct stat stat_buf; + bool reachable = true; + for (auto &v : lv) { + if (reachable) { + if (lstat(pos.c_str(), &stat_buf) < 0 || !S_ISDIR(stat_buf.st_mode)) { + reachable = false; + } + } + + if (reachable) { + if (v == ".") { + continue; + } else if (v == "..") { + pos = dirname(pos); + if (pos.empty()) { + pos = "/"; + } + continue; + } + } + + if (v.empty()) { + continue; + } + if (pos.back() != '/') { + pos += '/'; + } + pos += v; + if (pos.size() > MAX_PATH_LEN) { + return INVALID_PATH; + } + + if (reachable) { + std::string realpos; + if (pos == "/proc/self") { + realpos = "/proc/" + std::to_string(get_tgid_from_pid(pid)); + } else if (pos == "/proc/thread-self") { + realpos = + "/proc/" + std::to_string(get_tgid_from_pid(pid)) + "/" + std::to_string(pid); + } else { + if (lstat(pos.c_str(), &stat_buf) < 0) { + reachable = false; + continue; + } + if (!S_ISLNK(stat_buf.st_mode)) { + continue; + } + realpos = readlink(pos); + if (!is_len_valid_path(realpos)) { + return INVALID_PATH; + } + if (realpos[0] != '/') { + realpos = dirname(pos) + "/" + realpos; + } + } + + depth++; + realpos = abspath(realpos, pid); + depth--; + if (!is_len_valid_path(realpos)) { + return INVALID_PATH; + } + pos = realpos; + } + } + + return path_or_len_invalid(pos); +} +std::string getfdp(pid_t pid, int fd) { + if (fd == AT_FDCWD) { + return getcwdp(pid); + } else { + return abspath("/proc/self/fd/" + std::to_string(fd), pid); + } +} + +inline bool is_in_set_smart(std::string name, const std::set &s) { + if (name.size() > MAX_PATH_LEN) { + return false; + } + if (s.count(name)) { + return true; + } + int level; + for (level = 0; !name.empty(); name = dirname(name), level++) { + if (level == 1 && s.count(name + "/*")) { + return true; + } + if (s.count(name + "/")) { + return true; + } + } + if (level == 1 && s.count("/*")) { + return true; + } + if (s.count("/")) { + return true; + } + return false; +} + +inline bool is_writable_file(std::string name) { + if (name == "/") { + return writable_file_name_set.count("system_root"); + } + return is_in_set_smart(name, writable_file_name_set); +} +inline bool is_readable_file(const std::string &name) { + if (name == "/") { + return readable_file_name_set.count("system_root"); + } + return is_in_set_smart(name, readable_file_name_set); +} +inline bool is_statable_file(const std::string &name) { + if (name == "/") { + return statable_file_name_set.count("system_root"); + } + return is_in_set_smart(name, statable_file_name_set); +} +inline bool is_soft_ban_file(const std::string &name) { + if (name == "/") { + return soft_ban_file_name_set.count("system_root"); + } + return is_in_set_smart(name, soft_ban_file_name_set); +} + +void add_file_permission(const std::string &file_name, char mode) { + if (file_name.empty()) { + return; + } + if (mode == 'w') { + writable_file_name_set.insert(file_name); + } else if (mode == 'r') { + readable_file_name_set.insert(file_name); + } else if (mode == 's') { + statable_file_name_set.insert(file_name); + } + if (file_name == "system_root") { + return; + } + for (std::string name = dirname(file_name); !name.empty(); name = dirname(name)) { + statable_file_name_set.insert(name); + } +} + +void init_conf() { + const runp::config &config = run_program_config; + add_file_permission(config.work_path, 'r'); + add_file_permission(config.work_path + "/", 's'); + if (folder_program_type_set.count(config.type)) { + add_file_permission(realpath(config.program_name) + "/", 'r'); + } else { + add_file_permission(realpath(config.program_name), 'r'); + } + + std::vector loads; + loads.push_back("default"); + if (config.allow_proc) { + loads.push_back("allow_proc"); + } + if (config.type != "default") { + loads.push_back(config.type); + } + + for (std::string type : loads) { + if (allowed_syscall_list.count(type)) { + for (const auto &kv : allowed_syscall_list[type]) { + syscall_info_set[kv.first] = kv.second; + } + } + if (soft_ban_file_name_list.count(type)) { + for (const auto &name : soft_ban_file_name_list[type]) { + soft_ban_file_name_set.insert(name); + } + } + if (statable_file_name_list.count(type)) { + for (const auto &name : statable_file_name_list[type]) { + add_file_permission(name, 's'); + } + } + if (readable_file_name_list.count(type)) { + for (const auto &name : readable_file_name_list[type]) { + add_file_permission(name, 'r'); + } + } + if (writable_file_name_list.count(type)) { + for (const auto &name : writable_file_name_list[type]) { + add_file_permission(name, 'w'); + } + } + } + + for (const auto &name : config.readable_file_names) { + add_file_permission(name, 'r'); + } + for (const auto &name : config.writable_file_names) { + add_file_permission(name, 'w'); + } + + if (config.type == "python2" || config.type == "python3") { + soft_ban_file_name_set.insert(dirname(realpath(config.program_name)) + "/__pycode__/"); + } else if (config.type == "compiler") { + add_file_permission(config.work_path + "/", 'w'); + } + + readable_file_name_set.insert(writable_file_name_set.begin(), writable_file_name_set.end()); + statable_file_name_set.insert(readable_file_name_set.begin(), readable_file_name_set.end()); +} + +std::string read_string_from_addr(reg_val_t addr, pid_t pid) { + int max_len = MAX_PATH_LEN + sizeof(reg_val_t); + char res[max_len + 1], *ptr = res; + while (ptr != res + max_len) { + *(reg_val_t *)ptr = ptrace(PTRACE_PEEKDATA, pid, addr, NULL); + for (size_t i = 0; i < sizeof(reg_val_t); i++, ptr++, addr++) { + if (*ptr == 0) { + return res; + } + } + } + res[max_len] = 0; + return res; +} +std::string read_abspath_from_addr(reg_val_t addr, pid_t pid) { + std::string p = read_string_from_addr(addr, pid); + std::string a = abspath(p, pid); + if (run_program_config.need_show_trace_details) { + fprintf(stderr, "path : %s -> %s\n", p.c_str(), + is_len_valid_path(a) ? a.c_str() : "INVALID!"); + } + return a; +} +std::string read_abspath_from_fd_and_addr(reg_val_t fd, reg_val_t addr, pid_t pid) { + if (fd > MAX_FD_ID && (int)fd != AT_FDCWD) { + return INVALID_PATH; + } + std::string p = read_string_from_addr(addr, pid); + std::string a; + if (p.empty()) { + // this case is tricky + // if p is empty, in the following cases, Linux will understand the path as the path of fd: + // newfstatat + AT_EMPTY_PATH, linkat + AT_EMPTY_PATH, execveat + AT_EMPTY_PATH, readlinkat + // otherwise, the syscall will return with an error + // since fd is already opened, the program should have the permission to do the things + // listed above (no read -> write conversion, no deletion, no chmod, etc.) we just report + // this special case. the program will skip the permission check later + a = EMPTY_PATH_AFTER_FD; + } else { + a = abspath(p, pid, (int)fd); + } + if (run_program_config.need_show_trace_details) { + fprintf(stderr, "path : %d, %s -> %s\n", (int)fd, p.c_str(), + is_len_valid_path(a) ? a.c_str() : "INVALID!"); + } + return a; +} + +bool set_seccomp_bpf() { + scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRACE(0)); + if (!ctx) { + return false; + } + + try { + for (int no : supported_soft_ban_errno_list) { + if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(no), SYSCALL_SOFT_BAN_MASK | no, 0) < 0) { + throw std::system_error(); + } + } + + for (int i = 0; i < N_SYSCALL; i++) { + if (syscall_info_set[i].extra_check == ECT_NONE) { + if (syscall_info_set[i].should_soft_ban) { + if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), i, 0) < 0) { + throw std::system_error(); + } + } else { + if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, i, 0) < 0) { + throw std::system_error(); + } + } + } + } + seccomp_load(ctx); + } catch (std::system_error &e) { + seccomp_release(ctx); + return false; + } + seccomp_release(ctx); + return true; +} + +void rp_child_proc::set_error_for_suspicious(const std::string &error) { + this->suspicious = true; + this->error = "suspicious system call invoked: " + error; +} + +void rp_child_proc::set_error_for_kill() { + this->suspicious = false; + reg_val_t sig = this->syscall == __NR_tgkill ? this->reg.REG_ARG2 : this->reg.REG_ARG1; + this->error = "signal sent via " + syscall_name[this->syscall] + ": "; + if (sig != (unsigned)sig) { + this->error += "Unknown signal " + std::to_string(sig); + } else { + this->error += strsignal((int)sig); + } +} + +void rp_child_proc::soft_ban_syscall(int set_no = EPERM) { + this->reg.REG_SYSCALL = SYSCALL_SOFT_BAN_MASK | set_no; + ptrace(PTRACE_SETREGS, pid, NULL, &this->reg); +} + +bool rp_child_proc::check_file_permission(const std::string &op, const std::string &fn, char mode) { + std::string real_fn; + if (!fn.empty()) { + real_fn = mode == 'w' ? realpath_for_write(fn) : realpath(fn); + } + if (!is_len_valid_path(real_fn)) { + // path invalid or file not found + // ban this syscall softly + this->soft_ban_syscall(ENOENT); + return true; + } + + std::string path_proc_self = "/proc/" + std::to_string(get_tgid_from_pid(this->pid)); + if (real_fn.compare(0, path_proc_self.size() + 1, path_proc_self + "/") == 0) { + real_fn = "/proc/self" + real_fn.substr(path_proc_self.size()); + } else if (real_fn == path_proc_self) { + real_fn = "/proc/self"; + } + + bool ok; + switch (mode) { + case 'w': + ok = is_writable_file(real_fn); + break; + case 'r': + ok = is_readable_file(real_fn); + break; + case 's': + ok = is_statable_file(real_fn); + break; + default: + ok = false; + break; + } + + if (ok) { + return true; + } + + if (run_program_config.need_show_trace_details) { + fprintf(stderr, "check file permission %s : %s\n", op.c_str(), real_fn.c_str()); + fprintf(stderr, "[readable]\n"); + for (auto s : readable_file_name_set) { + std::cerr << s << '\n'; + } + fprintf(stderr, "[writable]\n"); + for (auto s : writable_file_name_set) { + std::cerr << s << '\n'; + } + } + + if (is_soft_ban_file(real_fn)) { + this->soft_ban_syscall(EACCES); + return true; + } else { + this->set_error_for_suspicious("intended to access a file without permission: " + op); + return false; + } +} + +bool rp_child_proc::check_safe_syscall() { + ptrace(PTRACE_GETREGS, pid, NULL, ®); + + int cur_instruction = ptrace(PTRACE_PEEKTEXT, pid, reg.rip - 2, NULL) & 0xffff; + if (cur_instruction != 0x050f) { + if (run_program_config.need_show_trace_details) { + fprintf(stderr, "informal syscall %d\n", cur_instruction); + } + this->set_error_for_suspicious("incorrect opcode " + std::to_string(cur_instruction)); + return false; + } + + if (0 > (long long int)reg.REG_SYSCALL || (long long int)reg.REG_SYSCALL >= N_SYSCALL) { + this->set_error_for_suspicious(std::to_string(reg.REG_SYSCALL)); + return false; + } + syscall = (int)reg.REG_SYSCALL; + if (run_program_config.need_show_trace_details) { + fprintf(stderr, "[syscall %s]\n", syscall_name[syscall].c_str()); + } + this->try_to_create_new_process = syscall == __NR_fork || syscall == __NR_clone + || syscall == __NR_clone3 || syscall == __NR_vfork; + + auto &cursc = syscall_info_set[syscall]; + + if (cursc.extra_check & ECT_CNT) { + if (cursc.max_cnt == 0) { + if (cursc.should_soft_ban) { + this->soft_ban_syscall(); + return true; + } else { + if (cursc.is_kill) { + this->set_error_for_kill(); + } else { + this->set_error_for_suspicious(syscall_name[syscall]); + } + return false; + } + } + cursc.max_cnt--; + } + + if (cursc.extra_check & ECT_KILL_SIG0_ALLOWED) { + reg_val_t sig = this->syscall == __NR_tgkill ? this->reg.REG_ARG2 : this->reg.REG_ARG1; + if (sig != 0) { + this->set_error_for_kill(); + return false; + } + } + + if (cursc.extra_check & ECT_FILE_OP) { + std::string fn; + if (cursc.extra_check & ECT_END_AT) { + fn = read_abspath_from_fd_and_addr(reg.REG_ARG0, reg.REG_ARG1, pid); + } else { + fn = read_abspath_from_addr(reg.REG_ARG0, pid); + } + + std::string textop = syscall_name[syscall]; + char mode = 'w'; + if (cursc.extra_check & ECT_CHECK_OPEN_FLAGS) { + reg_val_t flags = cursc.extra_check & ECT_END_AT ? reg.REG_ARG2 : reg.REG_ARG1; + switch (flags & O_ACCMODE) { + case O_RDONLY: + if ((flags & O_CREAT) == 0 && (flags & O_EXCL) == 0 && (flags & O_TRUNC) == 0) { + textop += " (for read)"; + mode = 'r'; + } else { + textop += " (for read & write)"; + } + break; + case O_WRONLY: + textop += " (for write)"; + break; + case O_RDWR: + textop += " (for read & write)"; + break; + default: + textop += " (with invalid flags)"; + break; + } + } else if (cursc.extra_check & ECT_FILE_S) { + mode = 's'; + } else if (cursc.extra_check & ECT_FILE_R) { + mode = 'r'; + } else if (cursc.extra_check & ECT_FILE_W) { + mode = 'w'; + } // else, error! + + if (run_program_config.need_show_trace_details) { + fprintf(stderr, "%-8s : %s\n", syscall_name[syscall].c_str(), fn.c_str()); + } + if (fn != EMPTY_PATH_AFTER_FD && !check_file_permission(textop, fn, mode)) { + return false; + } + + if (cursc.extra_check & ECT_FILE2_S) { + mode = 's'; + } else if (cursc.extra_check & ECT_FILE2_R) { + mode = 'r'; + } else if (cursc.extra_check & ECT_FILE2_W) { + mode = 'w'; + } else { + mode = '?'; + } + if (mode != '?') { + if (cursc.extra_check & ECT_END_AT) { + fn = read_abspath_from_fd_and_addr(reg.REG_ARG2, reg.REG_ARG3, pid); + } else { + fn = read_abspath_from_addr(reg.REG_ARG1, pid); + } + if (run_program_config.need_show_trace_details) { + fprintf(stderr, "%-8s : %s\n", syscall_name[syscall].c_str(), fn.c_str()); + } + if (fn != EMPTY_PATH_AFTER_FD && !check_file_permission(textop, fn, mode)) { + return false; + } + } + } + + if (cursc.extra_check & ECT_CLONE_THREAD) { + reg_val_t flags; + if (syscall == __NR_clone) { + flags = reg.REG_ARG0; + } else if (syscall == __NR_clone3) { + struct clone_args args; + read_memory_from_addr(reg.REG_ARG0, pid, &args, sizeof(args)); + flags = args.flags; + } else { + // Should not happen if syscalls are configured correctly + this->set_error_for_suspicious("clone/clone3 check on non-clone syscall"); + return false; + } + + if (!(flags & CLONE_THREAD)) { + this->set_error_for_suspicious("intended to create a new process"); + return false; + } + reg_val_t standard_flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM + | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID; + + if ((flags & standard_flags) != standard_flags) { + this->set_error_for_suspicious("intended to create a non-standard thread"); + return false; + } + } + + return true; +} diff --git a/web/Dockerfile b/web/Dockerfile index 3af78ff19..4ade309e8 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,32 +1,55 @@ -FROM ubuntu:20.04 +FROM ubuntu:24.04 MAINTAINER Baoshuo -LABEL org.opencontainers.image.source=https://github.com/UniversalOJ/UOJ-System + +LABEL org.opencontainers.image.source="https://github.com/UniversalOJ/UOJ-System" LABEL org.opencontainers.image.description="UOJ Web" -LABEL org.opencontainers.image.licenses=MIT +LABEL org.opencontainers.image.licenses="MIT" ARG CLONE_ADDFLAG ENV DEBIAN_FRONTEND=noninteractive -RUN dpkg -s gnupg 2>/dev/null || (apt-get update && apt-get install -y gnupg) &&\ -echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D858A0DF &&\ -apt-get update && apt-get install -y git vim ntp zip unzip curl wget apache2 libapache2-mod-xsendfile libapache2-mod-php php php-dev php-pear php-zip php-mysql php-mbstring php-gd php-intl php-xsl g++ make re2c libv8-7.5-dev libyaml-dev &&\ -yes | pecl install yaml &&\ -git clone https://github.com/phpv8/v8js.git --depth=1 -b 2.1.2 /tmp/pear/download/v8js-master && cd /tmp/pear/download/v8js-master &&\ -phpize && ./configure --with-php-config=/usr/bin/php-config --with-v8js=/opt/libv8-7.5 && make install && cd - +ENV LANG=C.UTF-8 +ENV TZ=Asia/Shanghai + +# https://github.com/phpv8/v8js/issues/517 +# Add an extra include to make phpv8 built on newer gcc +RUN \ + apt-get update && apt-get install -y --no-install-recommends software-properties-common gnupg curl ca-certificates && \ + echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D858A0DF && \ + add-apt-repository -y ppa:ondrej/php && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + git vim ntp zip unzip curl wget xxd mysql-client apache2 libapache2-mod-xsendfile \ + php7.4 libapache2-mod-php7.4 php7.4-dev php-pear php7.4-zip php7.4-mysql php7.4-mbstring php7.4-gd php7.4-intl php7.4-xsl \ + gcc-14 g++-14 make dpkg-dev re2c \ + libv8-7.5-dev libyaml-dev libseccomp-dev && \ + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 && \ + update-alternatives --install /usr/bin/cpp cpp-bin /usr/bin/cpp-14 100 && \ + yes | pecl install yaml && \ + git clone https://github.com/phpv8/v8js.git --depth=1 -b 2.1.2 /tmp/pear/download/v8js-master && \ + cd /tmp/pear/download/v8js-master && \ + phpize && \ + ./configure --with-php-config=/usr/bin/php-config --with-v8js=/opt/libv8-7.5 && \ + sed -i '1i#include ' v8js_object_export.cc && \ + make -j"$(nproc)" && make install && \ + cd / && rm -rf /tmp/pear/download/v8js-master \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* +# Add application files ADD . /opt/uoj WORKDIR /opt/uoj -# Install environment and set startup script -RUN sh web/install.sh -p && echo "\ -#!/bin/sh\n\ -if [ ! -f \"/var/uoj_data/.UOJSetupDone\" ]; then\n\ - cd /opt/uoj/web && sh install.sh -i\n\ -fi\n\ -service ntp start\n\ -service apache2 start\n\ -exec bash\n" >/opt/up && chmod +x /opt/up +# Install web environment and create startup script +RUN sh web/install.sh -p && \ + printf '%s\n' '#!/bin/sh' \ + 'if [ ! -f "/var/uoj_data/.UOJSetupDone" ]; then' \ + ' cd /opt/uoj/web && sh install.sh -i' \ + 'fi' \ + 'apache2ctl -D FOREGROUND' > /opt/up && \ + chmod +x /opt/up -ENV LANG=C.UTF-8 TZ=Asia/Shanghai EXPOSE 80 -CMD /opt/up +CMD ["/opt/up"] diff --git a/web/app/controllers/faq.php b/web/app/controllers/faq.php index b5fb975c6..84f277522 100644 --- a/web/app/controllers/faq.php +++ b/web/app/controllers/faq.php @@ -5,27 +5,27 @@ ?>
- +

来了?坐,欢迎来到

小熊像超人一样飞

-

众所周知,信息学的题目一般形式为:给出XXXXX,要你提交一份源代码,输出XXXXX,然后时限若干秒,内存若干兆,数据若干组,每组数据与答案进行比较,不对就不给分。

-

看起来挺合理的,但是总是有意外。比如要求输出一个浮点数,与答案接近就满分。于是只好引入Special Judge来判断选手输出的正确性。

-

但是还是有意外,比如提交两个程序,一个压缩另一个解压;比如提交答案题只用提交文件;比如给出音乐要求识别乐器,达到90%的正确率就算满分……

-

这个时候UOJ出现了,于是就使用了这套系统。Universal的中文意思是通用,之所以称之为UOJ,因为我们所有题目从编译、运行到评分,都可以由出题人自定义。

+

众所周知,信息学的题目一般形式为:给出 XXXXX,要你提交一份源代码,输出 XXXXX,然后时限若干秒,内存若干兆,数据若干组,每组数据与答案进行比较,不对就不给分。

+

看起来挺合理的,但是总是有意外。比如要求输出一个浮点数,与答案接近就满分。于是只好引入 Special Judge 来判断选手输出的正确性。

+

但是还是有意外,比如提交两个程序,一个压缩另一个解压;比如提交答案题只用提交文件;比如给出音乐要求识别乐器,达到 90% 的正确率就算满分……

+

这个时候 UOJ 出现了,于是 就使用了这套系统。Universal 的中文意思是通用,之所以称之为 UOJ,因为我们所有题目从编译、运行到评分,都可以由出题人自定义。

如果你正在为没有地方测奇奇怪怪的题目而苦恼,那么你来对地方了。

-

当然了,对于传统题的评测也做了特别支持。平时做题时我很难容忍的地方就是数据出水了导致暴力得了好多分甚至过了,而出题人却委屈地说,总共才一百分,卡了这个暴力就不能卡另一个暴力,所以暴力过了就过了吧。

-

所以我们引入了Extra Tests和Hack机制。每道传统题的数据都分为Tests和Extra Tests,Tests满分100分,如果你通过了所有的Tests,那么就会为你测Extra Tests。如果过了Tests但没过Extra Tests那么倒扣3分变为97分。Extra Tests的来源,一个是这道题没什么人可能会错的边界情况可以放在里面,另一个就是各位平时做题的时候,如果发现错误算法AC了,可以使用hack将其卡掉,会自动加入Extra Tests并重测。我们无法阻止暴力高分的脚步,但是不让他得满分还是有心里安慰作用的~

-

还有比赛功能可以承办比赛,赛制暂时只支持OI赛制。(不过你可以利用现有方案变相实现ACM赛制!)未来将支持更多种多样的赛制甚至自定义赛制。

-

目前刚刚起步,还有很多地方有待完善。想出题、想出比赛、发现BUG、发现槽点都可以联系我们,联系方式见下。

-

祝各位在玩得愉快!(求不虐萌萌哒服务器~求不虐萌萌哒测评机~!)

+

当然了, 对于传统题的评测也做了特别支持。平时做题时我很难容忍的地方就是数据出水了导致暴力得了好多分甚至过了,而出题人却委屈地说,总共才一百分,卡了这个暴力就不能卡另一个暴力,所以暴力过了就过了吧。

+

所以我们引入了 Extra Tests 和 Hack 机制。每道传统题的数据都分为 Tests 和 Extra Tests,Tests 满分 100 分,如果你通过了所有的 Tests,那么就会为你测 Extra Tests。如果过了 Tests 但没过 Extra Tests 那么倒扣 3 分变为 97 分。Extra Tests 的来源,一个是这道题没什么人可能会错的边界情况可以放在里面,另一个就是各位平时做题的时候,如果发现错误算法 AC 了,可以使用 hack 将其卡掉, 会自动加入 Extra Tests 并重测。我们无法阻止暴力高分的脚步,但是不让他得满分还是有心里安慰作用的~

+

还有比赛功能可以承办比赛,赛制暂时只支持 OI 赛制。(不过你可以利用现有方案变相实现 ACM 赛制!)未来将支持更多种多样的赛制甚至自定义赛制。

+

目前 刚刚起步,还有很多地方有待完善。想出题、想出比赛、发现 BUG、发现槽点都可以联系我们,联系方式见下。

+

祝各位在 玩得愉快!(求不虐萌萌哒服务器~求不虐萌萌哒测评机~!)

小熊抱抱

@@ -36,24 +36,23 @@
-

不提供头像存储服务。每到一个网站都要上传一个头像挺烦的对不对?我们支持Gravatar,请使用Gravatar吧!Gravatar是一个全球的头像存储服务,你的头像将会与你的电子邮箱绑定。在各大网站比如各种Wordpress还有各种OJ比如Vijos、Contest Hunter上,只要你电子邮箱填对了,那么你的头像也就立即能显示了!

-

快使用Gravatar吧! Gravatar地址:https://cn.gravatar.com/。进去后注册个帐号然后与邮箱绑定并上传头像,就ok啦!

+

不提供头像存储服务。每到一个网站都要上传一个头像挺烦的对不对?我们支持 Gravatar,请使用 Gravatar 吧!Gravatar 是一个全球的头像存储服务,你的头像将会与你的电子邮箱绑定。在各大网站比如各种 Wordpress 还有各种 OJ 比如 Vijos、Contest Hunter 上,只要你电子邮箱填对了,那么你的头像也就立即能显示了!

+

快使用 Gravatar 吧!Gravatar 地址:https://cn.gravatar.com/。进去后注册个帐号然后与邮箱绑定并上传头像,就 OK 啦!

-

默认的测评环境是 Ubuntu Linux 20.04 LTS x64。

-

C的编译器是 gcc 9.3.0,编译命令:gcc code.c -o code -lm -O2 -DONLINE_JUDGE

-

C++的编译器是 g++ 9.3.0,编译命令:g++ code.cpp -o code -lm -O2 -DONLINE_JUDGE。如果选择C++11会在编译命令后面添加-std=c++11

-

Java8的JDK版本是 openjdk 1.8.0_275,编译命令:javac code.java

-

Java11的JDK版本是 openjdk 11.0.9,编译命令:javac code.java

-

Pascal的编译器是 fpc 3.0.4,编译命令:fpc code.pas -O2

-

Python会先编译为优化过的字节码.pyo文件。支持的Python版本分别为Python 2.7和3.8。

+

默认的测评环境是 Ubuntu Linux 24.04 LTS x86_64。

+

C 的编译器是 gcc 14.2.0,编译命令:gcc code.c -o code -lm -O2 -DONLINE_JUDGE

+

C++ 的编译器是 g++ 14.2.0,编译命令:g++ code.cpp -o code -lm -O2 -DONLINE_JUDGE。如果选择 C++98/03/11… 会在编译命令后面添加 -std=c++98/03/11…

+

Java 的 JDK 版本是 openjdk 21.0.8,编译命令:javac code.java。如果选择 Java8/11/17… 会在编译命令后面添加 --release 8/11/17…

+

Pascal 的编译器是 fpc 3.2.2,编译命令:fpc code.pas -O2

+

Python 会先编译为优化过的字节码 .pyo 文件。支持的 Python 版本分别为 Python 2.7 和 3.12。

@@ -64,15 +63,15 @@
    -
  • Accepted: 答案正确。恭喜大佬,您通过了这道题。
  • -
  • Wrong Answer: 答案错误。仅仅通过样例数据的测试并不一定是正确答案,一定还有你没想到的地方。
  • -
  • Runtime Error: 运行时错误。像非法的内存访问,数组越界,指针漂移,调用禁用的系统函数都可能出现这类问题,请点击评测详情获得输出。
  • -
  • Time Limit Exceeded: 时间超限。请检查程序是否有死循环,或者应该有更快的计算方法。
  • -
  • Memory Limit Exceeded: 内存超限。数据可能需要压缩,或者您数组开太大了,请检查是否有内存泄露。
  • -
  • Output Limit Exceeded: 输出超限。你的输出居然比正确答案长了两倍!
  • -
  • Dangerous Syscalls: 危险系统调用,你是不是带了文件,或者使用了某些有意思的system函数?
  • -
  • Judgement Failed: 评测失败。可能是评测机抽风了,也可能是服务器正在睡觉;反正不一定是你的锅啦!
  • -
  • No Comment: 没有详情。评测机对您的程序无话可说,那么我们也不知道到底发生了什么...
  • +
  • Accepted:答案正确。恭喜大佬,您通过了这道题。
  • +
  • Wrong Answer:答案错误。仅仅通过样例数据的测试并不一定是正确答案,一定还有你没想到的地方。
  • +
  • Runtime Error:运行时错误。像非法的内存访问、数组越界、指针漂移、调用禁用的系统函数都可能出现这类问题,请点击评测详情获得输出。
  • +
  • Time Limit Exceeded:时间超限。请检查程序是否有死循环,或者应该有更快的计算方法。
  • +
  • Memory Limit Exceeded:内存超限。数据可能需要压缩,或者您数组开太大了,请检查是否有内存泄漏。
  • +
  • Output Limit Exceeded:输出超限。你的输出居然比正确答案长了两倍!
  • +
  • Dangerous Syscalls:危险系统调用,你是不是带了文件,或者使用了某些有意思的 system 函数?
  • +
  • Judgement Failed:评测失败。可能是评测机抽风了,也可能是服务器正在睡觉;反正不一定是你的锅啦!
  • +
  • No Comment:没有详情。评测机对您的程序无话可说,那么我们也不知道到底发生了什么……
@@ -83,22 +82,22 @@
-

没错就是这样!除非是特殊情况,测评程序时的栈大小与该题的空间限制是相等的!

+

没错就是这样!除非是特殊情况, 测评程序时的栈大小与该题的空间限制是相等的!

对于这类问题,我们在这里简单列一下可能原因:

    -
  • Linux中换行符是'\n'而windows中是'\r\n'(多一个字符)。有些数据在Windows下生成,而评测环境为Linux系统。这种情况在字符串输入中非常常见。
  • -
  • 评测系统建立在Linux下,可能由于使用了Linux的保留字而出现CE,但在Windows下正常。
  • -
  • Linux对内存的访问控制更为严格,因此在Windows上可能正常运行的无效指针或数组下标访问越界,在评测系统上无法运行。
  • -
  • 严重的内存泄露的问题很可能会引起系统的保护模块杀死你的进程。因此,凡是使用malloc(或calloc,realloc,new)分配而得的内存空间,请使用free(或delete)完全释放。
  • +
  • Linux 中换行符是 '\n' 而 Windows 中是 '\r\n'(多一个字符)。有些数据在 Windows 下生成,而 评测环境为 Linux 系统。这种情况在字符串输入中非常常见。
  • +
  • 评测系统建立在 Linux 下,可能由于使用了 Linux 的保留字而出现 CE,但在 Windows 下正常。
  • +
  • Linux 对内存的访问控制更为严格,因此在 Windows 上可能正常运行的无效指针或数组下标访问越界,在评测系统上无法运行。
  • +
  • 严重的内存泄漏问题很可能会引起系统的保护模块杀死你的进程。因此,凡是使用 malloc (或 calloc, realloc, new) 分配而得的内存空间,请使用 free (或 delete) 完全释放。
  • 当然数据可能真的有问题。但是如果不止一个人通过了这道题,那最好不要怀疑是数据的锅。反之,可以立即联系我们上报!
@@ -110,10 +109,10 @@
-

博客使用的是Markdown。(好吧……好简陋的……好多功能还没写……)

-

(喂喂喂我们是OJ好吗……要那么完善的博客功能干啥呢……?)

-

其实我觉得Markdown不用教!一学就会!

-

(完蛋了……好像没有Markdown的语法高亮……= =……)

+

博客使用的是 Markdown。(好吧……好简陋的……好多功能还没写……)

+

(喂喂喂我们是 OJ 好吗……要那么完善的博客功能干啥呢……?)

+

其实我觉得 Markdown 不用教!一学就会!

+

(完蛋了…… 好像没有 Markdown 的语法高亮……= =……)

我就只介绍最基本的功能好了。其它的自己探索吧~比如这里

**强调** = 强调

@@ -123,7 +122,7 @@

![这个文字在图挂了的时候会显示]() = 这个文字在图挂了的时候会显示


`rm orz` = rm orz

-

数学公式萌萌哒$(a + b)^2$萌萌哒 = 数学公式萌萌哒$(a + b)^2$萌萌哒

+

数学公式萌萌哒 $(a + b)^2$ 萌萌哒 = 数学公式萌萌哒 $(a + b)^2$ 萌萌哒


<!-- readmore --> = 在外面看这篇博客时会到此为止然后显示一个“阅读更多”字样


来个更大的例子:

@@ -147,10 +146,10 @@
 					\frac{-b + \sqrt{b^2 - 4ac}}{2a}
 					\end{equation}
 
-					#一级标题
-					##二级标题
-					###三级标题
-					####四级标题
+					# 一级标题
+					## 二级标题
+					### 三级标题
+					#### 四级标题
 					

会转换为:

#include <iostream>
@@ -164,10 +163,10 @@

二级标题

三级标题

四级标题

-

还有一个很重要的事情,就是你很容易以为在吃换行……

-

那是因为跟LaTeX一样,你需要一个空行来分段。你可以粗略地认为两个换行会被替换成一换行。(当然不完全是这样,空行是用来分段的,段落还有间距啊行首空两格啊之类的属性)

+

还有一个很重要的事情,就是你很容易以为 在吃换行……

+

那是因为跟 LaTeX 一样,你需要一个空行来分段。你可以粗略地认为两个换行会被替换成一换行。(当然不完全是这样,空行是用来分段的,段落还有间距啊行首空两格啊之类的属性)

唔……就介绍到这里吧。想要更详细的介绍上网搜搜吧~

-

(评论区是不可以用任何HTML滴~但是数学公式还是没问题滴)

+

(评论区是不可以用任何 HTML 滴~但是数学公式还是没问题滴)

@@ -177,12 +176,12 @@
-

唔……好问题。交互式的题一般给了一个头文件要你include进来,以及一个实现接口的源文件grader。好像大家对多个源文件一起编译还不太熟悉。

-

对于C++:g++ -o code grader.cpp code.cpp

-

对于C语言:gcc -o code grader.c code.c

-

如果你是悲催的电脑盲,实在不会折腾没关系!你可以把grader的文件内容完整地粘贴到你的code的include语句之后,就可以了!

-

什么你是萌萌哒Pascal选手?一般来说都会给个grader,你需要写一个Pascal单元。这个grader会使用你的单元。所以你只需要把源文件取名为单元名 + .pas,然后:

-

对于Pascal语言:fpc grader.pas

+

唔……好问题。交互式的题一般给了一个头文件要你 include 进来,以及一个实现接口的源文件 grader。好像大家对多个源文件一起编译还不太熟悉。

+

对于 C++:g++ -o code grader.cpp code.cpp

+

对于 C 语言:gcc -o code grader.c code.c

+

如果你是悲催的电脑盲,实在不会折腾没关系!你可以把 grader 的文件内容完整地粘贴到你的 code 的 include 语句之后,就可以了!

+

什么你是萌萌哒 Pascal 选手?一般来说都会给个 grader,你需要写一个 Pascal 单元。这个 grader 会使用你的单元。所以你只需要把源文件取名为单元名 + .pas,然后:

+

对于 Pascal 语言:fpc grader.pas

就可以啦!

@@ -193,12 +192,12 @@
-

如果你想出题、想办比赛、发现了BUG或者对网站有什么建议,可以通过下面的方式联系我们:

+

如果你想出题、想办比赛、发现了 BUG 或者对网站有什么建议,可以通过下面的方式联系我们:

    -
  • 私信联系
  • -
  • 邮件联系
  • +
  • 私信联系
  • +
  • 邮件联系
  • -
  • 你也可以进QQ群水水,群号是
  • +
  • 你也可以进 QQ 群水水,群号是
diff --git a/web/app/controllers/paste_post.php b/web/app/controllers/paste_post.php index 565cd734d..6f5f47f10 100644 --- a/web/app/controllers/paste_post.php +++ b/web/app/controllers/paste_post.php @@ -28,7 +28,7 @@ function handleUpload($zip_file_name, $content, $tot_size) { "file_name" => "paste.code" ] ], - 'uojRandAvaiablePasteFileName', + 'uojRandAvailablePasteFileName', 'handleUpload'); $paste_form->succ_href = '/paste'; $paste_form->runAtServer(); diff --git a/web/app/controllers/problem.php b/web/app/controllers/problem.php index 96f1718e9..f2ce2abb6 100644 --- a/web/app/controllers/problem.php +++ b/web/app/controllers/problem.php @@ -144,7 +144,7 @@ function handleCustomTestUpload($zip_file_name, $content, $tot_size) { if ($can_use_zip_upload) { $zip_answer_form = newZipSubmissionForm('zip_answer', $submission_requirement, - 'uojRandAvaiableSubmissionFileName', + 'uojRandAvailableSubmissionFileName', 'handleUpload'); $zip_answer_form->extra_validator = function() { global $ban_in_contest; @@ -159,7 +159,7 @@ function handleCustomTestUpload($zip_file_name, $content, $tot_size) { $answer_form = newSubmissionForm('answer', $submission_requirement, - 'uojRandAvaiableSubmissionFileName', + 'uojRandAvailableSubmissionFileName', 'handleUpload'); $answer_form->extra_validator = function() { global $ban_in_contest; @@ -175,7 +175,7 @@ function handleCustomTestUpload($zip_file_name, $content, $tot_size) { $custom_test_form = newSubmissionForm('custom_test', $custom_test_requirement, function() { - return uojRandAvaiableFileName('/tmp/'); + return uojRandAvailableFileName('/tmp/'); }, 'handleCustomTestUpload'); $custom_test_form->appendHTML(<< 0 && ($rest[0] === '.' || is_numeric($rest[0]))) { + return $file_name; + } + } + } + return null; + }; + + $src_name = $findSourceFile($name); + if (isset($src_name)) { echoFilePre($src_name); } else { echoFileNotFound($src_name); @@ -559,7 +572,7 @@ function($self) use ($problem_conf, $allow_files, $n_tests, $n_ex_tests) { $requirement = json_decode($problem['submission_requirement'], true); - $zip_file_name = uojRandAvaiableSubmissionFileName(); + $zip_file_name = uojRandAvailableSubmissionFileName(); $zip_file = new ZipArchive(); if ($zip_file->open(UOJContext::storagePath().$zip_file_name, ZipArchive::CREATE) !== true) { becomeMsgPage('提交失败'); diff --git a/web/app/controllers/subdomain/blog/blog_write.php b/web/app/controllers/subdomain/blog/blog_write.php index 461994542..24f81ff3e 100644 --- a/web/app/controllers/subdomain/blog/blog_write.php +++ b/web/app/controllers/subdomain/blog/blog_write.php @@ -1,6 +1,6 @@ name = 'blog'; if ($blog) { @@ -36,14 +36,14 @@ } else { $blog_editor->blog_url = null; } - + function updateBlog($id, $data) { DB::update("update blogs set title = '".DB::escape($data['title'])."', content = '".DB::escape($data['content'])."', content_md = '".DB::escape($data['content_md'])."', is_hidden = {$data['is_hidden']} where id = {$id}"); } function insertBlog($data) { DB::insert("insert into blogs (title, content, content_md, poster, is_hidden, is_draft, post_time) values ('".DB::escape($data['title'])."', '".DB::escape($data['content'])."', '".DB::escape($data['content_md'])."', '".Auth::id()."', {$data['is_hidden']}, {$data['is_draft']}, now())"); } - + $blog_editor->save = function($data) { global $blog; $ret = array(); @@ -77,12 +77,12 @@ function insertBlog($data) { } return $ret; }; - + $blog_editor->runAtServer(); ?> printHTML() ?> diff --git a/web/app/controllers/subdomain/blog/slide_write.php b/web/app/controllers/subdomain/blog/slide_write.php index 09144da41..404194c39 100644 --- a/web/app/controllers/subdomain/blog/slide_write.php +++ b/web/app/controllers/subdomain/blog/slide_write.php @@ -1,6 +1,6 @@ type = 'slide'; $blog_editor->name = 'blog'; @@ -37,14 +37,14 @@ } else { $blog_editor->blog_url = null; } - + function updateBlog($id, $data) { DB::update("update blogs set title = '".DB::escape($data['title'])."', content = '".DB::escape($data['content'])."', content_md = '".DB::escape($data['content_md'])."', is_hidden = {$data['is_hidden']} where id = {$id}"); } function insertSlide($data) { DB::insert("insert into blogs (type, title, content, content_md, poster, is_hidden, is_draft, post_time) values ('S', '".DB::escape($data['title'])."', '".DB::escape($data['content'])."', '".DB::escape($data['content_md'])."', '".Auth::id()."', {$data['is_hidden']}, {$data['is_draft']}, now())"); } - + $blog_editor->save = function($data) { global $blog; $ret = array(); @@ -74,12 +74,12 @@ function insertSlide($data) { } return $ret; }; - + $blog_editor->runAtServer(); ?> printHTML() ?> diff --git a/web/app/controllers/submission.php b/web/app/controllers/submission.php index 42e9bcbd6..630d82ac8 100644 --- a/web/app/controllers/submission.php +++ b/web/app/controllers/submission.php @@ -49,7 +49,7 @@ } } - $fileName = uojRandAvaiableTmpFileName(); + $fileName = uojRandAvailableTmpFileName(); $fileFullName = UOJContext::storagePath().$fileName; if ($_POST["input_upload_type"] == 'editor') { file_put_contents($fileFullName, $_POST['input_editor']); diff --git a/web/app/libs/uoj-data-lib.php b/web/app/libs/uoj-data-lib.php index b38356bb2..534762a39 100644 --- a/web/app/libs/uoj-data-lib.php +++ b/web/app/libs/uoj-data-lib.php @@ -48,6 +48,19 @@ private function check_conf_on($name) { return isset($this->problem_conf[$name]) && $this->problem_conf[$name] == 'on'; } + private function copy_source_files_to_prepare($name) { + $found = false; + foreach (array_keys($this->allow_files) as $file_name) { + if (strpos($file_name, $name) === 0) { + $rest = substr($file_name, strlen($name)); + if (strlen($rest) > 0 && ($rest[0] === '.' || is_numeric($rest[0])) && is_file("{$this->upload_dir}/$file_name")) { + $this->copy_to_prepare($file_name); + $found = true; + } + } + } + return $found; + } private function copy_to_prepare($file_name) { global $uojMainJudgerWorkPath; if (!isset($this->allow_files[$file_name])) { @@ -75,55 +88,56 @@ private function compile_at_prepare($name, $config = array()) { global $uojMainJudgerWorkPath; $include_path = "$uojMainJudgerWorkPath/include"; - if (!isset($config['src'])) { - $config['src'] = "$name.cpp"; - } - + $work_path = $this->prepare_dir; if (isset($config['path'])) { - exec("mv {$this->prepare_dir}/$name.cpp {$this->prepare_dir}/{$config['path']}/$name.cpp"); - $work_path = "{$this->prepare_dir}/{$config['path']}"; - } else { - $work_path = $this->prepare_dir; + $work_path .= '/' . $config['path']; } + + $cmd_prefix = "$uojMainJudgerWorkPath/run/run_program >{$this->prepare_dir}/run_compiler_result.txt --in=/dev/null --out=stderr --err={$this->prepare_dir}/compiler_result.txt --tl=15 --ml=2048 --ol=64 --type=compiler --add-readable-raw=$include_path/ --work-path=" . escapeshellarg($work_path); + + $compile_cmd = "$uojMainJudgerWorkPath/run/compile"; - $cmd_prefix = "$uojMainJudgerWorkPath/run/run_program >{$this->prepare_dir}/run_compiler_result.txt --in=/dev/null --out=stderr --err={$this->prepare_dir}/compiler_result.txt --tl=10 --ml=512 --ol=64 --type=compiler --work-path={$work_path}"; if (isset($config['need_include_header']) && $config['need_include_header']) { - exec("$cmd_prefix --add-readable-raw=$include_path/ /usr/bin/g++ -o $name {$config['src']} -I$include_path -lm -O2 -DONLINE_JUDGE"); - } else { - exec("$cmd_prefix /usr/bin/g++ -o $name {$config['src']} -lm -O2 -DONLINE_JUDGE"); + $compile_cmd .= " --cinclude=" . escapeshellarg($include_path); + } + if (isset($config['impl'])) { + $compile_cmd .= " --impl=" . escapeshellarg($config['impl']); + } + + $target_name = $name; + if (isset($config['path'])) { + $target_name = "../$name"; } + $compile_cmd .= " " . escapeshellarg($target_name); + + exec("$cmd_prefix $compile_cmd", $output, $ret); $fp = fopen("{$this->prepare_dir}/run_compiler_result.txt", "r"); if (fscanf($fp, '%d %d %d %d', $rs, $used_time, $used_memory, $exit_code) != 4) { $rs = 7; } fclose($fp); - + unlink("{$this->prepare_dir}/run_compiler_result.txt"); - + if ($rs != 0 || $exit_code != 0) { if ($rs == 0) { - throw new Exception("$name : compile error
\n" . uojFilePreview("{$this->prepare_dir}/compiler_result.txt", 100) . "\n
"); + throw new Exception("$name : compile error
\n" . uojFilePreview("{$this->prepare_dir}/compiler_result.txt", 4096) . "\n
"); } elseif ($rs == 7) { throw new Exception("$name : compile error. No comment"); } else { throw new Exception("$name : compile error. Compiler " . judgerCodeStr($rs)); } } - + unlink("{$this->prepare_dir}/compiler_result.txt"); - - if (isset($config['path'])) { - exec("mv {$this->prepare_dir}/{$config['path']}/$name.cpp {$this->prepare_dir}/$name.cpp"); - exec("mv {$this->prepare_dir}/{$config['path']}/$name {$this->prepare_dir}/$name"); - } } private function makefile_at_prepare() { global $uojMainJudgerWorkPath; $include_path = "$uojMainJudgerWorkPath/include"; - $cmd_prefix = "$uojMainJudgerWorkPath/run/run_program >{$this->prepare_dir}/run_makefile_result.txt --in=/dev/null --out=stderr --err={$this->prepare_dir}/makefile_result.txt --tl=10 --ml=512 --ol=64 --type=compiler --work-path={$this->prepare_dir}"; - exec("$cmd_prefix --add-readable-raw=$include_path/ /usr/bin/make INCLUDE_PATH=$include_path"); + $cmd_prefix = "$uojMainJudgerWorkPath/run/run_program >{$this->prepare_dir}/run_makefile_result.txt --in=/dev/null --out=stderr --err={$this->prepare_dir}/makefile_result.txt --tl=60 --ml=2048 --ol=64 --type=compiler --add-readable-raw=$include_path/ --work-path={$this->prepare_dir}"; + exec("$cmd_prefix /usr/bin/make INCLUDE_PATH=$include_path"); $fp = fopen("{$this->prepare_dir}/run_makefile_result.txt", "r"); if (fscanf($fp, '%d %d %d %d', $rs, $used_time, $used_memory, $exit_code) != 4) { @@ -135,7 +149,7 @@ private function makefile_at_prepare() { if ($rs != 0 || $exit_code != 0) { if ($rs == 0) { - throw new Exception("Makefile : compile error
\n" . uojFilePreview("{$this->prepare_dir}/makefile_result.txt", 100) . "\n
"); + throw new Exception("Makefile : compile error
\n" . uojFilePreview("{$this->prepare_dir}/makefile_result.txt", 4096) . "\n
"); } elseif ($rs == 7) { throw new Exception("Makefile : compile error. No comment"); } else { @@ -210,7 +224,9 @@ public function handle() { throw new Exception("" . htmlspecialchars($this->problem_conf['use_builtin_checker']) . " is not a valid checker"); } } else { - $this->copy_file_to_prepare('chk.cpp'); + if (!$this->copy_source_files_to_prepare('chk')) { + throw new UOJFileNotFoundException('chk.*'); + } $this->compile_at_prepare('chk', array('need_include_header' => true)); } } @@ -245,23 +261,29 @@ public function handle() { } if ($this->problem['hackable']) { - $this->copy_file_to_prepare('std.cpp'); + if (!$this->copy_source_files_to_prepare('std')) { + throw new UOJFileNotFoundException('std.*'); + } if (isset($this->problem_conf['with_implementer']) && $this->problem_conf['with_implementer'] == 'on') { $this->compile_at_prepare('std', array( - 'src' => 'implementer.cpp std.cpp', + 'impl' => 'implementer', 'path' => 'require' ) ); } else { $this->compile_at_prepare('std'); } - $this->copy_file_to_prepare('val.cpp'); + if (!$this->copy_source_files_to_prepare('val')) { + throw new UOJFileNotFoundException('val.*'); + } $this->compile_at_prepare('val', array('need_include_header' => true)); } if ($this->check_conf_on('interaction_mode')) { - $this->copy_file_to_prepare('interactor.cpp'); + if (!$this->copy_source_files_to_prepare('interactor')) { + throw new UOJFileNotFoundException('interactor.*'); + } $this->compile_at_prepare('interactor', array('need_include_header' => true)); } diff --git a/web/app/libs/uoj-html-lib.php b/web/app/libs/uoj-html-lib.php index fbf6981ad..a18e66b54 100644 --- a/web/app/libs/uoj-html-lib.php +++ b/web/app/libs/uoj-html-lib.php @@ -450,7 +450,14 @@ function echoPasteContent($paste) { switch ($file_language) { case 'C++': + case 'C++98': + case 'C++03': case 'C++11': + case 'C++14': + case 'C++17': + case 'C++20': + case 'C++23': + case 'C++26': $sh_class = 'sh_cpp'; break; case 'Python2': @@ -459,9 +466,16 @@ function echoPasteContent($paste) { break; case 'Java8': case 'Java11': + case 'Java17': + case 'Java21': $sh_class = 'sh_java'; break; case 'C': + case 'C89': + case 'C99': + case 'C11': + case 'C17': + case 'C23': $sh_class = 'sh_c'; break; case 'Pascal': @@ -487,7 +501,20 @@ function echoPasteContent($paste) { function echoSubmissionContent($submission, $requirement) { $zip_file = new ZipArchive(); $submission_content = json_decode($submission['content'], true); - $zip_file->open(UOJContext::storagePath().$submission_content['file_name']); + $zip_path = UOJContext::storagePath().$submission_content['file_name']; + $open_res = $zip_file->open($zip_path); + + if ($open_res !== true) { + echo '
'; + echo '
'; + echo '

Zip file open error

'; + echo '
'; + echo '
'; + echo '
'."\n"."Open zip file `".HTML::escape($zip_path)."` failed: $open_res"."\n".'
'; + echo '
'; + echo '
'; + return; + } $config = array(); foreach ($submission_content['config'] as $config_key => $config_val) { @@ -502,7 +529,14 @@ function echoSubmissionContent($submission, $requirement) { $footer_text = UOJLocale::get('problems::source code').', '.UOJLocale::get('problems::language').': '.$file_language; switch ($file_language) { case 'C++': + case 'C++98': + case 'C++03': case 'C++11': + case 'C++14': + case 'C++17': + case 'C++20': + case 'C++23': + case 'C++26': $sh_class = 'sh_cpp'; break; case 'Python2': @@ -511,9 +545,16 @@ function echoSubmissionContent($submission, $requirement) { break; case 'Java8': case 'Java11': + case 'Java17': + case 'Java21': $sh_class = 'sh_java'; break; case 'C': + case 'C89': + case 'C99': + case 'C11': + case 'C17': + case 'C23': $sh_class = 'sh_c'; break; case 'Pascal': diff --git a/web/app/libs/uoj-judger-lib.php b/web/app/libs/uoj-judger-lib.php index c86f26fb7..0029fd403 100644 --- a/web/app/libs/uoj-judger-lib.php +++ b/web/app/libs/uoj-judger-lib.php @@ -1,6 +1,6 @@ '.$new_msg_tot.''; } - + if (!isset($PageMainTitle)) { $PageMainTitle = UOJConfig::$data['profile']['oj-name']; } @@ -38,7 +38,7 @@ <?= isset($PageTitle) ? $PageTitle : UOJConfig::$data['profile']['oj-name-short'] ?> - <?= $PageMainTitle ?> - + @@ -48,10 +48,10 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -110,7 +110,7 @@ - + @@ -120,24 +120,24 @@ - + - + - + - + - + - + - + @@ -174,31 +174,31 @@ $REQUIRE_LIB['colorhelpers'] = ""; ?> - + - + - + - + - + @@ -214,12 +214,6 @@ - - - - + @@ -261,8 +255,8 @@

Logo

- + - +
diff --git a/web/app/views/slide.php b/web/app/views/slide.php index 9104f812c..ca47afb74 100644 --- a/web/app/views/slide.php +++ b/web/app/views/slide.php @@ -2,12 +2,12 @@ $content_p = strpos($content, "\n"); $slide_config = substr($content, 0, $content_p); $slide_content = substr($content, $content_p + 1); - + $slide_config = json_decode($slide_config, true); if ($slide_config === null) { die('error'); } - + if (!isset($slide_config['theme'])) { $slide_config['theme'] = 'moon'; } @@ -60,9 +60,9 @@ help: true, transition: 'slide', - + math: { - mathjax: '//cdn.bootcss.com/mathjax/2.6.0/MathJax.js', + mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2.7.7/MathJax.js', config: 'TeX-AMS_HTML-full' }, diff --git a/web/install.sh b/web/install.sh index 1b7705590..947a4dc96 100644 --- a/web/install.sh +++ b/web/install.sh @@ -62,13 +62,7 @@ file_put_contents('/var/www/uoj/app/.config.php', "include/uoj_work_path.h <'); var show_help_lang = function() { - if ($(this).val() == 'Java8' || $(this).val() == 'Java11') { + if ($(this).val() == 'Java8' || $(this).val() == 'Java11' || $(this).val() == 'Java17' || $(this).val() == 'Java21') { div_help_language.text('注意:Java 程序源代码中不应指定所在的 package。我们会在源代码中找到第一个被定义的类并以它的 main 函数为程序入口点。'); } else { div_help_language.text('');