(base) MacBook-Pro-2:pdftex zmx$ latex
This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017) (preloaded format=latex)
restricted \write18 enabled.
**\s^EH
entering extended mode
LaTeX2e <2017-04-15>
Babel <3.10> and hyphenation patterns for 84 language(s) loaded.
! Undefined control sequence.
<*> \s
^^EH
? 1
! Text line contains an invalid character.
<*> \s^^E
H
? Q
OK, entering \batchmode
上面的例子中,^E 在 MacOS 上是 ctrl+e。显然它是一个无效字符,所以当我输入“1”时,它输出了一条错误消息“文本行包含无效字符”。然后我输入“Q”,它似乎没有开始运行,而是要求进一步输入。
我的问题是为什么在“Q”之后需要进一步输入?
实际上,我正在为我的项目调试 pdftex。在 pdftex 1.40.18 上,如果您输入:
\s^EH
1
问
五
如果有人对这个错误感兴趣,请随时与我讨论。
答案1
原始发帖人:您能给我发电子邮件至 karl@tug.org 吗?您发现了原始 TeX 中的一个错误,DEK 肯定会想把圣塞里夫银行的一大笔钱转给您 :)。
“Q 之后的进一步输入”和“分段错误”具有相同的根本原因:在这种不寻常的交互序列中,TeX 的数据陷入了不一致的状态。
我已向 TL svn r55767 提交了修复程序(感谢 David Fuchs)。它适用于除 LuaTeX 之外的所有 TeX 变体,LuaTeX 必须单独修复。经过更多测试后,我预计 Akira 将在他的 w32tex 发行版中推出新的二进制文件,供任何可以使用并想尝试的人使用。TL 二进制文件不会为此更新。
罪魁祸首是您的输入导致在交互 = 批处理模式时调用模块 83 <获取用户的建议...>。但这绝不应该发生。随后发生了混乱。
需要“H”(或任何文本)来导致 web2c 中的崩溃,因为该崩溃是由于尝试将文本写入关闭的 \write 流而发生的。(选择器从 16(no_print)减少到 15,对应于 \write15。就像我说的,造成严重破坏。)
根据记录,我可以通过运行 tex -ini <invalid.in 来重现该错误,其中 invalid.in 是一个包含以下四行的文本文件:
\catcode`\^=7 \catcode`\^^?=15 \s^^?E 1 q v
很高兴你找到了它。
答案2
作为TeX 版本 3.141592653 和 Metafont 版本 2.71828182,该错误已修复。本答案的其余部分仅适用于以前的版本。
注意:我刚刚发现 Metafont 中也存在此错误。请参阅此答案的结尾。
为了记录的目的,这里准确说明了导致错误发生的原因,因为我花了一段时间才弄清楚。
关键的是两个整数变量selector
和interaction
。我们先来关注interaction
第一个,因为它比较简单。它用来控制 TeX 是否停止与用户交互,它有四个可能的值:
当
interaction
=error_stop_mode
= 3 时,如果发生错误(§82、§530),或者如果\pausing
设置为正值(§363),或者如果\read
用于从终端获取输入(§484),或者如果interrupt
在某些点为非零[在扫描标记列表之后(§324),在读取一行输入之后(§343),以及在处理连字期间(§753、§911)],TeX 将停止。当
interaction
=scroll_mode
= 2 时,TeX 不会在发生非致命错误时停止,除非问题是找不到文件,在这种情况下 TeX 仍会提示用户输入新文件名(§530)。当
interaction
=nonstop_mode
= 1 时,TeX 不会停止,除非发生致命错误,或者进入需要用户输入的情况——即,如果\end
文件中不存在命令(§360),如果命令要求从终端输入(§484),或者找不到文件(§530)。(这些情况被视为致命情况,但如果是或则\read
不会 。)interaction
scroll_mode
error_stop_mode
当
interaction
=batch_mode
= 0 时,TeX 的行为与 时interaction
一样nonstop_mode
,除了省略终端输出之外 (§75、§90、§92、§1328)。这很重要。
请注意,用户交互级别会随着 值的interaction
增加而增加。最初,interaction
设置为error_stop_mode
(§74)。
该selector
变量控制 TeX 的各种文本打印例程将其输出发送到何处。在 TeX82 中,它有 22 个可能的值,范围从 0 到 21。当 0 ≤ selector
≤ 15 时,它表示使用 打开的文件之一
\openout
。15 以上的值selector
具有以下含义:
当
selector
=no_print
= 16 时,打印无进展。当
selector
=term_only
= 17 时,打印仅发送到终端。当
selector
=log_only
= 18 时,打印仅转到成绩单文件。当
selector
=term_and_log
= 19 时,打印转到终端和成绩单文件。当
selector
=pseudo
= 20 时,字符将“打印”到缓冲区供程序使用show_context
,这一过程称为“伪打印”(§315)。此设置与我们无关。当
selector
=new_string
= 21 时,字符将附加到字符串内存中(如果还有空间)。此设置与我们无关。
最初selector
是term_only
(§55、§1332),因为尚未打开任何笔录文件。
selector
和的值interaction
大多是独立的。但是,正如您所预料的,当 时interaction
,batch_mode
不应selector
为
term_only
或term_and_log
。在一种情况下,它term_only
在 §535 中无条件地(虽然是暂时的,因为先前的值已在 §534 中保存)设置为 ,而不管interaction
。但一般的想法是, 当且仅当> 时,selector
将是term_only
或;特别是,当= 时,将是或。term_and_log
interaction
batch_mode
selector
term_only
term_and_log
interaction
error_stop_mode
当 TeX 希望从终端读取一行时,它会调用term_input
(通常通过prompt_input
;参见§71)。此例程巧妙地利用了selector
的可能值之间的数字关系,以便在适当的情况下回显输入行。该程序假定在输入 时selector
必须是 或
term_only
(其他值没有意义)。因此可以减少并无条件打印用户输入的行;如果是,它就变成 ,这是正确的,因为该行已经被回显了(由于终端的性质),如果是,它就变成,这是正确的,因为该行必须写入转录文件。term_and_log
term_input
term_input
selector
selector
term_only
no_print
selector
term_and_log
log_only
现在让我们看一下该error
例程。其顶层如下所示(§82):
procedure error;
label continue, exit;
var …;
begin
…
if interaction = error_stop_mode then
⟨Get the user’s advice and return⟩;
…
exit:
end;
⟨获取用户的建议…⟩ 的提纲如下:
loop
begin continue:
clear_for_error_prompt;
prompt_input("? ");
if last = first then
return;
c ← buffer[first]
if c ≥ "a" then
c ← c + "A" − "a"; {convert to uppercase}
⟨Interpret code c and return if done⟩;
⟨Print the menu of available options⟩;
end
⟨打印菜单...⟩ 部分听起来就是这样,不同之处在于,E
如果没有打开输入文件,则不会列出键入以编辑输入文件的选项,并且如果
deletions_allowed
为假,则不会列出键入数字以删除标记的选项(为了阻止超过两级的递归error
)。
⟨解释代码…⟩ 中有趣的部分c
是一个大case
语句,它打开了 的值c
。(无趣的部分实际上是 ⟨打印菜单…⟩。我把它移走了,以使整个循环流程更清晰。)在以下描述中,控制转移以大胆的。
如果
c
是十进制数字,并且如果可以删除令牌,则删除用户指定的令牌数量,并且控制权归continue
。如果
c
是"E"
,那么(在 TeX82 中)会告知用户要编辑哪个文件的哪一行,并且 TeX终止。如果
c
是"H"
,则打印帮助信息,并且控制权归continue
。如果
c
是"I"
,那么从终端读取一行输入作为 TeX 下一步要处理的内容,并且控制权归exit
通过return
宏。如果
c
是"Q"
,则interaction
变为batch_mode
,selector
减少(以抑制终端输出),并且控制权归exit
。如果
c
是"R"
,则interaction
变为nonstop_mode
并且控制权归exit
。如果
c
是"S"
,则interaction
变为scroll_mode
并且控制权归exit
。如果
c
是"X"
,则interaction
变为scroll_mode
和 TeX 终止。否则什么也不会发生;控制失败到⟨打印菜单...⟩我们回到循环顶部。
如果用于调试的代码没有被注释掉, 那么也会出现c
= 的情况。"D"
控制权归 continue
然后。
[关于案例 5、6、7 有一点值得注意:每次改变 都
interaction
伴随着一条消息,说OK, entering
,然后是新模式;例如,当你输入 时S
,TeX 说
OK, entering scrollmode
。然后程序执行print("...")
,所以消息最终是
OK, entering scrollmode...
。然而,在案例 5 中,selector
在省略号之前递减,所以如果selector
是,它最终会进入转录文件,如果是,它最终会进入term_and_log
任何地方;不会出现在终端上。Knuth 在他的第六个练习的答案中承认了这一点selector
term_only
...
TeX:程序他在 TUGboat 上发表过(练习
这里,答案
这里)。
删除过程非常简单。首先,保存某些全局变量(、、和)的值。然后cur_tok
将 设置 为 false — 这是停止不必要递归的另一种措施,因为如果发生中断并且为 true,可能会调用cur_cmd
。 接下来,将设置为用户输入的数字。执行以下循环:cur_chr
align_state
OK_to_interrupt
error
OK_to_interrupt
c
while c > 0 do
begin
get_token; {one-level recursive call of error is possible}
decr(c);
end
因此,只需读取并忽略标记即可删除标记。get_token
就我们的目的而言,该过程可以视为与 相同get_next
。递归可能发生,因为get_next
可能导致error
被调用。 中可能出现的大多数错误情况get_next
最终会终止程序;它们是致命错误。但有一个直接调用error
,当读取无效字符时发生(§346)。deletions_allowed
变量在调用之前设置为 false,之后设置为 true。
那么问题是什么?让我们考虑一下当您启动纯 TeX 并输入麻烦的输入时会发生什么。(我使用纯 TeX 是因为^^?
已经被定为非法。)首先\s^^?E
输入 以响应提示**
。由于输入的第一个字符是\
(= escape
),TeX 将其视为常规代码(即,它不假设您想要\input
一个名为 的文件\s^^?E
;参见§1337)。\s
读取 ,TeX 尝试扩展名为 的控制序列s
。由于\s
没有定义,expand
例程调用error
(§370)。
此时,interaction
是error_stop_mode
并且selector
是term_only
。(这就是为什么错误必须发生在输入的第一行;否则记录文件将被打开并selector
发生变化。)§83 中的循环开始。然后您输入1
(这是上面列出的情况 1),§88 开始执行,并由
get_next
调用get_token
。无效字符^^?
(ASCII 代码 127 = '177
= "7F
;参见附录 CTeXbook) 被读取,控制权移至 §346。该error
例程被再次调用。
interaction
和的值selector
没有改变,因此错误对话框像以前一样输入。现在您输入Q
。§86 中的代码运行;interaction
变为batch_mode
,并selector
减少到no_print
。控制从error
返回到get_next
,跳过无效字符并读取E
输入中的左侧字符。然后我们回到error
;请记住我们处于情况 1,因此控制权上升到continue
并且对话框循环再次开始。
此时,interaction
isbatch_mode
和selector
is no_print
= 16,并且我们处于§83中的循环顶部,应该执行仅有的如果
interaction
= error_stop_mode
。现在,所有拼图碎片都已就位。prompt_input
宏首先尝试打印?
;由于 的值为 ,因此什么也没有显示selector
。然后prompt_input
调用
term_input
,它确实执行了input_ln(term_in, true)
;这就是为什么 TeX 等待输入,即使它应该处于批处理模式。无效字符后面必须有文本的原因是,否则 TeX 将遇到输入的结尾(在get_next
,§360)并报告致命错误 [ *** (job aborted, no legal \end found)
]。fatal_error
过程 (§93) 调用
normalize_selector
(§92),旨在避免我所描述的情况!
接下来,term_input
减少selector
;其值变为 15。如果您输入任何内容来响应不可见的?
,term_input
则将尝试通过调用print
中的每个字符来打印它buffer
,最终将调用print_char
。(简单练习:为什么不能直接term_input
调用print_char
?)的值selector
不是上面列举的六个重要值之一,因此print_char
尝试打印到write_file[selector]
。的元素write_file
属于类型alpha_file
,并且它们都不是实际的开放流,因此现在发生的情况取决于系统。在 Web2C 中,结果是putc
将使用空指针作为其第二个参数进行调用(请参阅
fixwrites.c
),这将导致分段错误。 ∎
现在我们知道了哪里出了问题,那么该如何修复呢?在 TeX Live 和最近的调整中,§83 被改为在循环开始时进行测试,因此现在看起来像
loop
begin continue:
if interaction ≠ error_stop_mode then
return;
clear_for_error_prompt;
prompt_input("? ");
if last = first then
return;
c ← buffer[first]
if c ≥ "a" then
c ← c + "A" − "a" {convert to uppercase}
⟨Interpret code c and return if done⟩;
end
(看7 月 6 日提交的c
。这里我没有像之前一样将⟨打印菜单…⟩从⟨解释代码…⟩中取出。)
在研究了原始代码后,我想出了以下替代解决方案。首先,我们更改error
的顶层(§82),以便
if interaction = error_stop_mode then
⟨Get the user's advice and return⟩;
是
while interaction = error_stop_mode do
⟨Get the user's advice and return⟩;
然后我们将第 83 条改为
begin
clear_for_error_prompt;
prompt_input("? ");
if last = first then
return;
c ← buffer[first];
if c ≥ "a" then
c ← c + "A" − "a"; {convert to uppercase}
⟨Interpret code c and return if done⟩;
continue:
end
还有其他更激进的选择。我们可以做同样的改变,但删除continue
§83 中的标签,并将 ⟨Interpret code c
…⟩ 改为类似
if (c ≥ "0") ∧ (c ≤ "9") ∧ deletions_allowed then
⟨Delete c − "0" tokens⟩
else
if (c = "E") ∧ (base_ptr > 0) then
…
else
case c of
debug "D"
begin
debug_help;
end;
gubed
"H":
⟨Print the help information⟩;
"I":
⟨Introduce new material from the terminal and return⟩;
"Q", "R", "S":
⟨Change the interaction level and return⟩;
"X":
begin
interaction ← scroll_mode;
jump_out;
end;
othercases
⟨Print the menu of available options⟩
endcases
已从goto continue
删除代码、调试代码和帮助显示代码中删除。在我看来,这更糟糕,因为即使为c
或"E"
为数字,菜单也可能被打印,这一点并不明显。
除此以外,其他地方error
也可以改变。我们可以做出
term_input
或明确验证∈ { , }prompt_input
的假设 。例如,可以将其扩展为selector
term_only
term_and_log
prompt_input(#)
begin
if (selector ≠ term_only) ∧ (selector ≠ term_and_log) then
confusion("selector");
wake_up_terminal();
print(#);
term_input;
end
当然,只有当程序中还存在更多此类错误时,这才会有帮助。
附录:Metafont 和 TeX 共享许多编程,事实上它们的例程版本error
几乎相同。因此,这个错误在两个程序中都发生并不奇怪。不过,Metafont 中的情况没有那么糟糕;不会发生分段错误。这次有问题的第一行是\1:=^Ax
,其中^A
是 control+ a
。(任何无效字符都可以,但必须直接输入,因为 Metafont 没有与 TeX^^
语法等效的语法。)您将收到错误
Improper `:=' will be changed to `='.
其余交互与之前一样。你输入1
,Metafont 会谴责无效字符,然后你输入q
,Metafont 会在进入批处理模式后等待输入。
当然还有其他方法可以导致错误。您可以说\1;^Ax
,但您必须删除两个令牌,而不是一个。
上面关于 TeX 的大部分说明都适用于 Metafont,尽管许多部分编号不同。这种selector
恶作剧不会发生,因为 Metafont 认为它介于 0 和 5 之间,如果不是,则不会执行任何操作。
答案3
tex
我可以使用以下测试文档重现该行为
\tracingall
\catcode`\^^E=15
\s^^EH
如果我跑tex test
,我会得到
This is TeX, Version 3.14159265 (TeX Live 2020) (preloaded format=tex)
(./testinv.tex
{vertical mode: \tracingstats}
{\tracingpages}
{\tracingoutput}
{\tracinglostchars}
{\tracingmacros}
{\tracingparagraphs}
{\tracingrestores}
{\showboxbreadth}
{\showboxdepth}
{\catcode}
{undefined}
! Undefined control sequence.
l.3 \s
^^EH
? 1
! Text line contains an invalid character.
l.3 \s^^E
H
? q
OK, entering \batchmode
>
最后一行表示只有按下回车键才会出现的 shell 提示符。
为什么有人说这种行为在 TL 2020 中无法重现?好问题。直到 2018 年,LaTeX 才将几个字符(包括)设为无效,以捕获错误输入。当 UTF-8 成为默认输入编码并且现在不再在格式中分配类别代码 15^^E
时,情况发生了变化。^^E
如果我击中r
,我会得到
This is TeX, Version 3.14159265 (TeX Live 2020) (preloaded format=tex)
(./testinv.tex
{vertical mode: \tracingstats}
{\tracingpages}
{\tracingoutput}
{\tracinglostchars}
{\tracingmacros}
{\tracingparagraphs}
{\tracingrestores}
{\showboxbreadth}
{\showboxdepth}
{\catcode}
{undefined}
! Undefined control sequence.
l.3 \s
^^EH
? 1
! Text line contains an invalid character.
l.3 \s^^E
H
? r
OK, entering \nonstopmode...
l.3 \s^^EH
?
这暗示无效字符确实被忽略并且1
指令(忽略一个标记)尚未执行并且 TeX 仍在等待用户输入。
故事的寓意是:无效字符确实是无效的,在错误恢复期间删除令牌时不会被考虑。
另一方面,
? q
OK, entering \batchmode
行应该表示它的意思(但它不是,就像当r
被击中时一样)。这可能是一个真正的错误。
较短的测试文件是
\s^^?H
执行操作时1
并q
在提示符下查看日志文件,但再次按下回车键
This is TeX, Version 3.14159265 (TeX Live 2020) (preloaded format=tex 2020.4.17) 27 JUN 2020 10:34
**test
(./test.tex
! Undefined control sequence.
l.1 \s
^^?H
? 1
! Text line contains an invalid character.
l.1 \s^^?
H
? q
OK, entering \batchmode...
l.1 \s^^?H
? )
! Emergency stop.
<*> test
*** (job aborted, no legal \end found)
No pages of output.