Компилятор XDS хороший, но он ошибается. Иногда просто не может что-то переварить, а иногда и создаёт программу, не соответствующую исходнику. С первым можно как-то мириться, ведь ошибка видна уже в процессе компиляции, и можно попытаться написать по-другому. А во втором случае приходится проявлять особую осторожность: из-за нестабильной природы ошибок здесь не всегда поможет даже тестирование.
За время работы с XDS (с 2007 по 2012 год) я повстречал несколько ошибок. Для всех из них я смог создать проекты минимального размера - такие, чтобы присутствие именно этой конкретной ошибки стало очевидно и надёжно воспроизводилось. Этот набор тестов я и предлагаю вашему вниманию. С помощью этого набора я оцениваю прогресс когда выходит очередная версия XDS. Правда, понятно, что такой метод оценивания не учитывает привнесённые ошибки новых версий, которые ещё только предстоит найти. Да и невозможность воспроизвести ошибку иной раз может не означать, что ошибка исправлена. Может быть, она просто надёжнее спряталась. К сожалению, разработчики XDS не радуют подробной отчётностью о проделанной работе, поэтому и приходится изобретать свои способы оценки.
Кроме того, учёт найденных сбоев просто позволяет следовать принципу "знай свой инструмент", и учит избегать тех или иных "опасных" (в смысле ненадёжности) конструкций, опций и т. п.
Все сбои воспроизводятся в свежеустановленном XDS. Если требуются какие-либо дополнительные опции, то они включены в исходный текст модулей в виде директив компилятора <* *>. Консольная выдача записана с помощью последней версии XDS, в которой удалось воспроизвести проблему. Если в какой-то версии ошибка не проявляется, об этом указано в комментариях.
В самом деле, почему бы не начать сразу с выводов? Как видно из последующих примеров, директиву PROCINLINE лучше отключить и не включать. Заинлайненые процедуры не дружат с локальными указателями, будь то простые переменные или поля RECORD'ов. Встречаются и более серьёзные проблемы, пропадающие при отключении директивы PROCINLINE.
Все эти найденные ошибки - это не повод сказать "плохой компилятор". Наоборот, компилятор весьма неплох. Просто иной раз вылетает исключение "invalid location" на пустом месте, и не знаешь, что с этим делать и как исправлять. А когда ошибка локализована, и найдена конкретная причина, то появляется определённость. И тогда понимаешь: отдельные мелкие недоработки есть, но их мало, а в остальном-то всё прекрасно работает!
Для этого теста нам понадобятся два модуля: Definition.def и Test.ob2.
<* ENUMSIZE="4" *><* M2EXTENSIONS+ *>
DEFINITION MODULE ['StdCall'] Definition;
IMPORT SYSTEM;
TYPE
FORMAT = (
FIF_BMP,
FIF_ICO
);
CONST
FIF_UNKNOWN = SYSTEM.CAST (FORMAT, 0FFFFFFFFh);
END Definition.
(* Why is the assignment below impossible, although the comparison works? *)
<* NOOPTIMIZE+ *> (* This is to avoid constant check elimination. *)
<* MAIN+ *>
MODULE Test;
IMPORT
Definition, Out, SYSTEM;
VAR
fmt: Definition.FORMAT;
BEGIN
(* The SYSTEM.VAL version works as expected. *)
fmt := SYSTEM.VAL (Definition.FORMAT, Definition.FIF_UNKNOWN);
(* But the following line would not compile (E122 expression out of bounds) *)
fmt := Definition.FIF_UNKNOWN;
(* The comparison below is compiled without any complaints. *)
IF fmt = Definition.FIF_UNKNOWN THEN
Out.String ('Constant check 1: OK.');
Out.Ln;
ELSE
Out.String ('Constant check 1: FAILED.');
Out.Ln;
END;
END Test.
>xc =make Test.ob2 && Test.exe O2/M2 development system v2.60 TS (c) 1991-2011 Excelsior, LLC. (build 16.11.2011) XDS Modula-2 v2.40 [x86, v1.50] - build 16.11.2011 Compiling "Definition.def" no errors, no warnings, lines 15, time 0.00, new symfile XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011 Compiling "test.ob2" * [test.ob2 15.11 E122] * expression out of bounds fmt := $Definition.FIF_UNKNOWN; errors 1, no warnings, lines 24, time 0.02 ------------------------------------------------------------------- files: 2 errors: 1(0) lines 39 time: 0:00 speed 10000 l/m
Странность здесь заключается в том, что константа Definition.FIF_UNKNOWN по определению имеет тип Definition.FORMAT, но при этом не может напрямую быть присвоена переменной fmt того же типа. Если же с помощью SYSTEM.VAL привести тип к тому, что и так имеется, то присваивание почему-то работает. Возникает вопрос: раз нужно приведение типа, то имеет ли константа изначально тип FORMAT или нет? Тот факт, что IF компилируется без вопросов, говорит о том, что таки да, тип тот самый, иначе была бы ошибка несовпадения типов при сравнении. Но почему же не компилируется присваивание?
<* PROCINLINE+ *> (* Allow procedure inlining. *)
<* MAIN+ *>
MODULE Test;
(* ------------------------------------------------------------------------
* (C) 2012 by Alexander Iljin
* ------------------------------------------------------------------------ *)
IMPORT
Out;
PROCEDURE InitModule; (* This procedure will be inlined. *)
VAR
wr: RECORD
int: INTEGER; (* Any variable will do *)
END;
BEGIN
(* The following code results in compiler crash with message "compilation
* aborted: ASSERT(FALSE,9999) at line 133 of formOMF.ob2". *)
IF wr.int # 0 THEN
Out.String('Test passed!');
END;
END InitModule;
BEGIN
InitModule;
END Test.
>xc =make Test.ob2 && Test.exe O2/M2 development system v2.60 TS (c) 1991-2011 Excelsior, LLC. (build 16.11.2011) XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011 Compiling "Test.ob2" Generating Test * [Test.ob2 28.04 W330] * function InitModule inlined $InitModule; * [*** 0.00 F450] * compilation aborted: ASSERT(FALSE,9999) at line 133 of formOMF.ob2
Если отключить директиву PROCINLINE, то модуль компилируется успешно.
MODULE Test;
(* ------------------------------------------------------------------------
* (C) 2011 by Alexander Iljin
* ------------------------------------------------------------------------ *)
VAR
tmp: SHORTINT; (* any integer type will do *)
BEGIN
INC(tmp, LONG(1000000)); (* Compiler breaks with error F450: "compilation
* aborted: invalid case in CASE statement".
* Remove "LONG" and it will work. *)
END Test.>xc Test.ob2 XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011 Compiling "Test.ob2" * [*** 0.00 F450] * compilation aborted: invalid case in CASE statement at line 1000 of pcF.ob2
MODULE Test;
(* ------------------------------------------------------------------------
* (C) 2010 by Alexander Iljin
* ------------------------------------------------------------------------ *)
CONST
c = LONG (0 + 1); (* This makes the compiler abort with "invalid location".
* The value of the constant does not matter, and neither
* does its name. Just as soon as there is an expression
* inside the LONG: +, -, *. *)
END Test.>xc Test.ob2 XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011 Compiling "Test.ob2" * [*** 0.00 F450] * compilation aborted: invalid location
LONG здесь - бессмысленная операция с точки зрения языка Oberon. Я ожидал бы, чтобы компилятор её проигнорировал. Возможно, выдал бы какое-то предупреждение или сообщил об ошибке в данном месте. Сообщение же "compilation aborted: invalid location" говорит о том, что внутри самого компилятора есть ошибка, не позволяющая ему штатно обработать ситуацию.
<* O2EXTENSIONS+ *> (* Allow string concatenation for the constants below. *)
<* MAIN+ *>
MODULE Test;
(* ------------------------------------------------------------------------
* (C) 2009 by Alexander Iljin
* ------------------------------------------------------------------------ *)
CONST
Str1 = 'some string' + 09X; (* this is compiled, no error *)
Tab = 09X;
Str2 = 'some string' + Tab; (* same thing, but produces a compilation error:
* incompatible types:
* "string constant (SS)"
* "CHAR"
*)
END Test.
>xc =make Test.ob2 && Test.exe
O2/M2 development system v2.60 TS (c) 1991-2011 Excelsior, LLC. (build 16.11.2011)
XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011
Compiling "Test.ob2"
* [Test.ob2 12.25 E029]
* incompatible types:
"string constant (SS)"
"CHAR"
Str2 = 'some string' $+ Tab; (* same thing, but produces a compilatio...
errors 1, no warnings, lines 18, time 0.01
Не знаю, есть ли действительные причины для этой ошибки, или это просто недоработка. Просто мне такое непоследовательное поведение компилятора кажется странным, поэтому я считаю это ошибкой. Даже если бы константа Tab была определена в другом модуле, нет причины запрещать использовать её в выражении.
<* GENPTRINIT+ *> (* Make sure local pointers are initialized. *)
<* PROCINLINE+ *> (* Allow procedure inlining. *)
<* MAIN+ *>
MODULE Test;
(* ------------------------------------------------------------------------
* (C) 2012 by Alexander Iljin
* ------------------------------------------------------------------------ *)
IMPORT
Out;
PROCEDURE InitModule; (* This procedure will be inlined. *)
VAR
ptr: POINTER TO ARRAY OF CHAR;
BEGIN
(* Since GENPTRINIT is ON, the 'ptr' variable must be set NIL, but that
* does not happen. The compiler thinks that there will be an 'invalid
* location' trap and simply generates a call to the trap routine. This is
* very strange, since we don't even dereference the pointer here, so there
* is no reason for that kind of error. *)
IF ptr # NIL THEN
Out.String('Error!');
ELSE
Out.String('Test passed.');
END;
END InitModule;
BEGIN
InitModule;
END Test.
>xc =make Test.ob2 && Test.exe
O2/M2 development system v2.60 TS (c) 1991-2011 Excelsior, LLC. (build 16.11.2011)
XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011
Compiling "Test.ob2"
Generating Test
* [Test.ob2 23.07 W304]
* possibly used before definition "ptr"
IF $ptr # NIL THEN
* [Test.ob2 23.11 W915]
* invalidLocation exception will be raised here
IF ptr $# NIL THEN
* [Test.ob2 24.07 W311]
* unreachable code
$Out.String('Error!');
* [Test.ob2 26.07 W311]
* unreachable code
$Out.String('Test passed.');
* [Test.ob2 31.04 W330]
* function InitModule inlined
$InitModule;
no errors, warnings 5, lines 32, time 0.21, new symfile
New "tmp.lnk" is generated using template "d:/Programs/Dev/XDS/bin/xc.tem"
XDS Link Version 2.13.3 Copyright (c) Excelsior 1995-2009.
No errors, no warnings
#RTS: unhandled exception #3: invalid location at line 23 of Test.ob2
File errinfo.$$$ created.
EAX = 00000001 EBX = 7FFD5000
ECX = 00000000 EDX = 0044B000
ESI = 00000000 EDI = 00000000
EBP = 0006FFB8 ESP = 0006FF7C
EIP = 00401041
STACK:
0006FF7C: 00000003 0040E018 00000017 0040E034
0006FF8C: 0006FFA4 00092170 00000001 001E8480
0006FF9C: 003D0900 0040C36A 00000001 00092170
0006FFAC: 00092198 00000001 00092170 0006FFF0
0006FFBC: 0006FFE0 0040CB91 7C816FD7 00000000
0006FFCC: 00000000 7FFD5000 8054A6ED 0006FFC8
0006FFDC: 89288930 FFFFFFFF 7C839AA8 7C816FE0
0006FFEC: 00000000 00000000 00000000 0040CB60
Если отключить директиву PROCINLINE, то тест проходит успешно. Т. е. именно комбинация PROCINLINE+ и GENPTRINIT+ приводит к ошибке кодогенерации.
<* GENPTRINIT+ *> (* Make sure local pointers are initialized, including RECORD fields. *)
<* PROCINLINE+ *> (* Allow procedure inlining. *)
<* MAIN+ *>
MODULE Test;
(* ------------------------------------------------------------------------
* (C) 2012 by Alexander Iljin
* ------------------------------------------------------------------------ *)
IMPORT
Out;
TYPE
(* A stack-based RECORD type with a POINTER field. *)
Writer = RECORD
ptr: POINTER TO ARRAY OF CHAR; (* any pointer type will do *)
END;
PROCEDURE InitModule; (* This procedure will be inlined. *)
VAR
wr: Writer;
BEGIN
(* Since GENPTRINIT is ON, the field wr.ptr must be set NIL, but that
* does not happen if the procedure is inlined. The compiler simply does
* not generate the initialization code (typically that would be a "push 0"
* instruction), and whatever is in the stack is left in the pointer field.
* In real-life programs this leads to random 'invalid location' traps. *)
IF wr.ptr # NIL THEN
Out.String('Error!');
ELSE
Out.String('Test passed.');
END;
END InitModule;
BEGIN
InitModule;
END Test.
>xc =make Test.ob2 && Test.exe O2/M2 development system v2.60 TS (c) 1991-2011 Excelsior, LLC. (build 16.11.2011) XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011 Compiling "Test.ob2" Generating Test * [Test.ob2 37.04 W330] * function InitModule inlined $InitModule; no errors, warnings 1, lines 38, time 0.01, new symfile New "tmp.lnk" is generated using template "d:/Programs/Dev/XDS/bin/xc.tem" XDS Link Version 2.13.3 Copyright (c) Excelsior 1995-2009. No errors, no warnings Error!
Отличие от предыдущего теста заключается в том, что здесь неинициализированный указатель находится внутри стековой записи (RECORD). Как в и предыдущем случае, если отключить директиву PROCINLINE, то тест проходит успешно. Т. е. именно комбинация PROCINLINE+ и GENPTRINIT+ приводит к ошибке кодогенерации.
Полный проект состоит из трёх модулей, проектного файла с опциями и двух командных bat-файлов. Для проверки нужно запустить файл Run.bat.
>Run.bat
O2/M2 development system v2.60 TS (c) 1991-2011 Excelsior, LLC. (build 16.11.2011)
Make project "Test.prj"
XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011
Compiling "Display.ob2"
no errors, no warnings, lines 24, time 0.01, new symfile
XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011
Compiling "BugHere.ob2"
Generating BugHere
* [BugHere.ob2 70.15 W330]
* function NewField inlined
f[i] := $NewField(i);
* [BugHere.ob2 84.14 W330]
* function NewFields inlined
p.new := $NewFields(); (* manually inlining NewFields removes the ...
no errors, warnings 2, lines 90, time 0.05, new symfile
XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011
Compiling "Main.ob2"
no errors, no warnings, lines 9, time 0.03, new symfile
New "obj/tmp.lnk" is generated using template "d:/Programs/Dev/XDS/bin/xc.tem"
-------------------------------------------------------------------
files: 3 errors: 0(2) lines 123 time: 0:00 speed 10000 l/m
XDS Link Version 2.13.3 Copyright (c) Excelsior 1995-2009.
No errors, no warnings
Start Init - End Init
The Bug has disappeared
O2/M2 development system v2.60 TS (c) 1991-2011 Excelsior, LLC. (build 16.11.2011)
Make project "Test.prj"
XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011
Compiling "BugHere.ob2"
Generating BugHere
* [BugHere.ob2 68.15 W330]
* function NewField inlined
f[i] := $NewField(i);
* [BugHere.ob2 82.14 W330]
* function NewFields inlined
p.new := $NewFields(); (* manually inlining NewFields removes the ...
no errors, warnings 2, lines 88, time 0.05, new symfile
XDS Oberon-2 v2.40 [x86, v1.50] - build 16.11.2011
Compiling "Main.ob2"
no errors, no warnings, lines 9, time 0.03
-------------------------------------------------------------------
files: 2 errors: 0(2) lines 97 time: 0:00 speed 10000 l/m
XDS Link Version 2.13.3 Copyright (c) Excelsior 1995-2009.
No errors, no warnings
#RTS: unhandled exception #3: invalid location
File errinfo.$$$ created.
EAX = 00000000 EBX = 0040F000
ECX = 00000000 EDX = 00000001
ESI = 00000000 EDI = 100340C0
EBP = 100340D0 ESP = 0006FF44
EIP = 004010CD
STACK:
0006FF44: 00000000 00000000 0040F000 0006FF68
0006FF54: 00000010 00000000 00000000 0006FFB8
0006FF64: 00000001 1003C078 100340D0 100340C0
0006FF74: 00000000 00000000 7FFDF000 0006FFB8
0006FF84: 00401228 0040F29C 0006FFA4 00092170
0006FF94: 00000001 001E8480 003D0900 0040C78A
0006FFA4: 00000001 00092170 00092198 00000001
0006FFB4: 00092170 0006FFF0 0006FFE0 0040CFB1
Start Init
The Bug is still present
The Bug was reproduced successfully.
Комбинация из следующих опций приводит к воспроизведению данной ошибки: NOPTRALIAS+, CHECKNIL-, DOREORDER+, GENDEBUG-, PROCINLINE+. Если удалить любую из них из файла Test.prj (заменив тем самым на значение по умолчанию), то ошибка не проявится.
Данная ошибка была выделена из большого проекта, что стоило немалых усилий. На помощь была призвана бесплатная версия дизассемблера IDA Pro. Естественно, в процессе минимизации потребовалось автоматизировать компиляцию и проверку наличия именно искомой ошибки. Для этого были использованы утилиты sed.exe, grep.exe, интерпретатор cmd.exe и отладочная выдача с помощью стандартного модуля Out. Чтобы выполнить проверку, запустите файл Run.bat. Он скомпилирует тестовый проект и вызовет CheckBug.bat, чтобы убедиться, что ошибки нет. (Изначально ошибка "исправлена" двумя пустыми вызовами Disp.String в процедуре BugHere.NewField.) После этого sed.exe используется для того, чтобы удалить вызовы Disp.String, и проект компилируется заново. CheckBug.bat вызывается снова, чтобы убедиться, что удаление этих вызовов вызвало ошибку. Вывод о наличии ошибки выводится в консоль (последняя строка выдачи).
<* NOOPTIMIZE+ *> (* This option is here to prevent removing the ASSERTs:
* compiler thinks that the conditions are TRUE and
* eliminates the checks. *)
<* MAIN+ *>
MODULE Test;
(* ------------------------------------------------------------------------
* (C) 2009 by Alexander Iljin
* ------------------------------------------------------------------------ *)
IMPORT
SYSTEM, Out;
PROCEDURE ReproduceBug ();
TYPE
LowHigh = RECORD
low, high: INTEGER;
END;
VAR
lh: LowHigh;
longLow, longHigh: LONGINT;
BEGIN
lh.high := 2000H;
lh.low := 1000H;
ASSERT (lh.high = 2000H, 20);
ASSERT (lh.low = 1000H, 21);
longHigh := lh.high; (* this works *)
longLow := SYSTEM.VAL (LONGINT, lh.low); (* same thing, but doesn't work *)
ASSERT (longHigh = 2000H, 60);
ASSERT (longLow = 1000H, 61); (* error is here: longLow = 20001000H *)
Out.String ('Test passed.');
END ReproduceBug;
BEGIN
ReproduceBug;
END Test.
>xc =make Test.ob2 && Test.exe O2/M2 development system v2.51 (c) 1999-2003 Excelsior, LLC. (build 10.05.2005) XDS Oberon-2 v2.40 [x86, v1.50] - build 10.05.2005 Compiling "Test.ob2" no errors, no warnings, lines 36, time 0.01, new symfile New "tmp.lnk" is generated using template "d:/Programs/Dev/XDS/bin/xc.tem" XDS Link Version 2.6 Copyright (c) 1995-2001 Excelsior No errors, no warnings #RTS: unhandled exception #61: ASSERT(FALSE, 61) File errinfo.$$$ created.
В XDS v2.60 beta данная ошибка не воспроизводится, на консоль выдаётся "Test passed".
<* O2EXTENSIONS+ *> (* This allows applying a type guard to a procedure result. *)
<* MAIN+ *>
MODULE Test;
IMPORT Out;
TYPE
Ansector = POINTER TO AnsectorDesc;
AnsectorDesc = RECORD
END;
Descendant = POINTER TO DescendantDesc;
DescendantDesc = RECORD (AnsectorDesc)
END;
VAR
desc: Descendant;
counter: INTEGER;
PROCEDURE GetThis (): Ansector;
VAR res: Descendant;
BEGIN
INC(counter);
NEW (res);
RETURN res
END GetThis;
BEGIN
counter := 0;
desc := GetThis ()(Descendant);
(* Although the GetThis procedure is called only once, it will be executed
* twice because of the type guard bug. *)
CASE counter OF
| 1: Out.String('Test passed.');
| 2: Out.String('Test failed!');
END;
END Test.
>xc =make Test.ob2 && Test.exe O2/M2 development system v2.51 (c) 1999-2003 Excelsior, LLC. (build 10.05.2005) XDS Oberon-2 v2.40 [x86, v1.50] - build 10.05.2005 Compiling "Test.ob2" no errors, no warnings, lines 37, time 0.02, new symfile New "tmp.lnk" is generated using template "d:/Programs/Dev/XDS/bin/xc.tem" XDS Link Version 2.6 Copyright (c) 1995-2001 Excelsior No errors, no warnings Test failed!
В XDS v2.60 beta данная ошибка не воспроизводится, на консоль выдаётся "Test passed".
Нашёл ошибки, создал минимальные проекты для их воспроизведения и разместил в данном документе: Александр Ильин. Если вы знаете о каких-то ошибках, не указанных здесь, пожалуйста, пишите, присылайте проекты для воспроизведения. Если не можете создать маленький модуль, то присылайте большой проект, разберёмся!