feat(题目): 运行时错误告诉学生第几行、为什么出错(Python 看回溯,C 看信号),不泄露测试数据
原来学生只看到「运行时错误」四个字:主判题 output: false,Python 回溯没回来; C 的信号值在 info 里,而 info 只给管理员。 - Python 判出 RE 时,拿第一个失败测试点单独重跑一次(内联测试点 + output: true), 从回溯取行号(扣掉模板前置行)、异常类型和按句式归的类别。主判题仍不开 output, 免得每个点最多 16MB 的输出回到 worker - C / C++ 取失败点的信号和退出码(段错误提示 scanf 漏 &、return 非 0 等) - 写进 statistic_info.runtime_error;不存异常原文(里面常带测试点输入),名字只在 出现在学生代码里时才存——raise Exception(input()) 实测只落下类型 - 前端按类型翻成中文、摆出出错那行并在编辑器标红;比赛提交不诊断(ACM 只报对错) - 没有诊断的 RE 不再说「代码格式写错了」 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2a4405d2ea
commit
db70ab62e8
7 files changed
+391
-11
No files matched your search
@@ -63,7 +63,7 @@ onUnmounted(clearErrorMark)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-card embedded>
|
||||
<n-card embedded class="explain-card">
|
||||
<n-flex vertical :size="12">
|
||||
<template v-if="explanation">
|
||||
<div class="explain">
|
||||
@@ -95,6 +95,11 @@ onUnmounted(clearErrorMark)
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 结果弹窗不限宽,长句子不折行会把弹窗撑出屏幕 */
|
||||
.explain-card {
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.explain {
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import { clearErrorMark, showErrorMark } from "oj/problem/utils/errorMark"
|
||||
import { explainRuntimeError, type RuntimeErrorInfo } from "oj/problem/utils/runtimeError"
|
||||
|
||||
/**
|
||||
* 运行时错误的中文说明卡片(诊断由后端 judge/runtime-diagnosis.ts 给)。
|
||||
* 知道是第几行的,就把那一行代码摆出来,并在编辑器里标红;卸掉时清掉标记。
|
||||
*/
|
||||
const props = defineProps<{
|
||||
info: RuntimeErrorInfo
|
||||
/** 这次提交的代码,用来摆出出错的那一行 */
|
||||
code: string
|
||||
}>()
|
||||
|
||||
const message = computed(() => explainRuntimeError(props.info))
|
||||
|
||||
const sourceLine = computed(() => {
|
||||
const line = props.info.line
|
||||
if (!line) return null
|
||||
const text = props.code.split("\n")[line - 1]
|
||||
return text?.trim() ? text.trim() : null
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.info.line,
|
||||
(line) => {
|
||||
if (line) showErrorMark(line, null, null)
|
||||
else clearErrorMark()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
onUnmounted(clearErrorMark)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-card embedded class="explain-card">
|
||||
<n-flex vertical :size="12">
|
||||
<div class="explain">
|
||||
<b v-if="info.line">第 {{ info.line }} 行运行时出错:</b>{{ message }}
|
||||
</div>
|
||||
<pre v-if="sourceLine" class="explain-code">{{ sourceLine }}</pre>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 结果弹窗不限宽,长句子不折行会把弹窗撑出屏幕 */
|
||||
.explain-card {
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.explain {
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.explain-code {
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 15px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
background-color: rgba(208, 48, 80, 0.12);
|
||||
}
|
||||
</style>
|
||||
@@ -15,6 +15,7 @@ import type { Submission } from "utils/types"
|
||||
import SubmissionResultTag from "shared/components/SubmissionResultTag.vue"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
import PythonErrorExplain from "./PythonErrorExplain.vue"
|
||||
import RuntimeErrorExplain from "./RuntimeErrorExplain.vue"
|
||||
import { useAIStream } from "shared/composables/aiStream"
|
||||
import { submitHintFeedback } from "oj/api"
|
||||
import { MdPreview } from "md-editor-v3"
|
||||
@@ -102,21 +103,34 @@ const pythonCompileError = computed(() => {
|
||||
})
|
||||
|
||||
// 错误信息格式化
|
||||
/**
|
||||
* 运行时错误的诊断(行号 + 归类),交给 RuntimeErrorExplain。比赛提交、诊断失败的
|
||||
* 没有这个字段(见后端 judge/run.ts 的 diagnoseRuntimeError),走下面 msg 的通用提示。
|
||||
*/
|
||||
const runtimeError = computed(() => {
|
||||
const submission = props.submission
|
||||
if (!submission || submission.result !== SubmissionStatus.runtime_error) return null
|
||||
return submission.statisticInfo?.runtime_error ?? null
|
||||
})
|
||||
|
||||
const msg = computed(() => {
|
||||
if (!props.submission) return ""
|
||||
// 走 PythonErrorExplain 那张中文卡片,英文原文在它的折叠区里
|
||||
if (pythonCompileError.value) return ""
|
||||
// 走 PythonErrorExplain / RuntimeErrorExplain 那两张中文卡片
|
||||
if (pythonCompileError.value || runtimeError.value) return ""
|
||||
|
||||
let msg = ""
|
||||
const result = props.submission.result
|
||||
|
||||
// 编译错误或运行时错误时给出提示;
|
||||
// SQL 题的运行错误多半是"查询题里写了增删改"这类被判题拒绝的语句,err_info 已说明原因,不套这句
|
||||
if (
|
||||
(result === SubmissionStatus.compile_error || result === SubmissionStatus.runtime_error) &&
|
||||
props.submission.language !== "SQL"
|
||||
) {
|
||||
msg += "请仔细检查,看看代码的格式是不是写错了!\n\n"
|
||||
if (props.submission.language !== "SQL") {
|
||||
if (result === SubmissionStatus.compile_error) {
|
||||
msg += "请仔细检查,看看代码的格式是不是写错了!\n\n"
|
||||
} else if (result === SubmissionStatus.runtime_error) {
|
||||
// 原来和编译错误共用「代码格式写错了」那句,可运行时错误恰恰是格式没错、跑起来才出事
|
||||
msg +=
|
||||
"程序运行到一半出错,停下来了。检查一下输入是怎么读的、下标有没有越界、除数是不是 0。\n\n"
|
||||
}
|
||||
}
|
||||
|
||||
if (result !== SubmissionStatus.ast_check_failed && props.submission.statisticInfo?.err_info) {
|
||||
@@ -270,12 +284,14 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
||||
vertical
|
||||
v-if="
|
||||
pythonCompileError ||
|
||||
runtimeError ||
|
||||
msg ||
|
||||
infoTable.length ||
|
||||
submission.statisticInfo?.ast_results?.length
|
||||
"
|
||||
>
|
||||
<PythonErrorExplain v-if="pythonCompileError" :err-info="pythonCompileError" />
|
||||
<RuntimeErrorExplain v-if="runtimeError" :info="runtimeError" :code="submission.code" />
|
||||
<n-card v-if="submission.statisticInfo?.ast_results?.length" embedded>
|
||||
<n-flex vertical :size="8">
|
||||
<n-flex
|
||||
|
||||
Reference in new issue
Block a user