Since quite some time now I can no longer decently build SDCC in-tree on Ubuntu 20.04 LTS.
Configure does not complain:
$ ./configure -q --disable-pic14-port --disable-pic16-port
The following languages will be built: c
But make does:
$ make > /dev/null
In file included from /usr/include/string.h:495,
from sdcdb.h:47,
from sdcdb.c:24:
In function ‘strncpy’,
inlined from ‘readCdb’ at sdcdb.c:418:11,
inlined from ‘cmdFile’ at sdcdb.c:816:12:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:106:10: warning: ‘__builtin_strncpy’ specified bound depends on the length of the source argument [-Wstringop-overflow=]
106 | return __builtin___strncpy_chk (__dest, __src, __len, __bos (__dest));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sdcdb.c: In function ‘cmdFile’:
sdcdb.c:418:36: note: length computed here
418 | strncpy(currl->line, bp, strlen(bp)-1);
| ^~~~~~~~~~
cmd.c: In function ‘displayAll’:
cmd.c:3194:11: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
3194 | if (sym = symLookup (dsym->name, cctxt))
| ^~~
cmdpars.y: warning: 55 shift/reduce conflicts [-Wconflicts-sr]
cmdpars.y: note: rerun with option '-Wcounterexamples' to generate conflict counterexamples
asxxxx.c: In function ‘asxxxx_bfd_is_target_special_symbol’:
asxxxx.c:1068:55: warning: unused parameter ‘a’ [-Wunused-parameter]
1068 | static bool asxxxx_bfd_is_target_special_symbol(bfd * a, asymbol * b)
| ~~~~~~^
asxxxx.c:1068:68: warning: unused parameter ‘b’ [-Wunused-parameter]
1068 | static bool asxxxx_bfd_is_target_special_symbol(bfd * a, asymbol * b)
| ~~~~~~~~~~^
configure: WARNING: libdebuginfod is missing or unusable; some features may be unavailable.
/home/maarten/sdcc/support/sdbinutils/binutils/sysinfo.y: warning: 1 shift/reduce conflict [-Wconflicts-sr]
/home/maarten/sdcc/support/sdbinutils/binutils/sysinfo.y: note: rerun with option '-Wcounterexamples' to generate conflict counterexamples
/home/maarten/sdcc/support/sdbinutils/binutils/defparse.y: warning: 27 shift/reduce conflicts [-Wconflicts-sr]
/home/maarten/sdcc/support/sdbinutils/binutils/defparse.y: note: rerun with option '-Wcounterexamples' to generate conflict counterexamples
/home/maarten/sdcc/support/sdbinutils/binutils/rcparse.y: warning: 58 shift/reduce conflicts [-Wconflicts-sr]
/home/maarten/sdcc/support/sdbinutils/binutils/rcparse.y: warning: 10 reduce/reduce conflicts [-Wconflicts-rr]
/home/maarten/sdcc/support/sdbinutils/binutils/rcparse.y: note: rerun with option '-Wcounterexamples' to generate conflict counterexamples
/home/maarten/sdcc/support/sdbinutils/binutils/mcparse.y: warning: 1 shift/reduce conflict [-Wconflicts-sr]
/home/maarten/sdcc/support/sdbinutils/binutils/mcparse.y: note: rerun with option '-Wcounterexamples' to generate conflict counterexamples
In file included from /usr/include/string.h:495,
from makebin.c:31:
In function ‘strncpy’,
inlined from ‘main’ at makebin.c:819:15:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:106:10: warning: ‘__builtin_strncpy’ specified bound 2 equals destination size [-Wstringop-truncation]
106 | return __builtin___strncpy_chk (__dest, __src, __len, __bos (__dest));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
configure: WARNING: fixed-point is not supported for this target, ignored
configure: WARNING:
*** Makeinfo is missing or too old.
*** Info documentation will not be built.
basename: missing operand
Try 'basename --help' for more information.
/home/maarten/sdcc/support/cpp/gcc/configure: line 19705: test: =: unary operator expected
/home/maarten/sdcc/support/cpp/gcc/configure: line 24018: test: -gt: unary operator expected
/home/maarten/sdcc/support/cpp/gcc/configure: line 24019: test: -eq: unary operator expected
Links are now set up to build a native compiler for x86_64-pc-linux-gnu.
expr.cc: In function ‘unsigned int cpp_classify_number(cpp_reader*, const cpp_token*, const char**, location_t)’:
expr.cc:808:18: warning: format not a string literal and no format arguments [-Wformat-security]
808 | 0, message);
| ^
expr.cc:811:39: warning: format not a string literal and no format arguments [-Wformat-security]
811 | virtual_location, 0, message);
| ^
expr.cc:821:34: warning: format not a string literal and no format arguments [-Wformat-security]
821 | virtual_location, 0, message);
| ^
macro.cc: In member function ‘vaopt_state::update_type vaopt_state::update(const cpp_token*)’:
macro.cc:186:23: warning: format not a string literal and no format arguments [-Wformat-security]
186 | vaopt_paste_error);
| ^
macro.cc:215:24: warning: format not a string literal and no format arguments [-Wformat-security]
215 | vaopt_paste_error);
| ^
macro.cc: In function ‘cpp_macro* create_iso_definition(cpp_reader*)’:
macro.cc:3704:58: warning: format not a string literal and no format arguments [-Wformat-security]
3704 | cpp_error (pfile, CPP_DL_ERROR, paste_op_error_msg);
| ^
macro.cc:3719:58: warning: format not a string literal and no format arguments [-Wformat-security]
3719 | cpp_error (pfile, CPP_DL_ERROR, paste_op_error_msg);
| ^
ar: `u' modifier ignored since `D' is the default (see `U')
/bin/sh: /home/maarten/sdcc/support/cpp/missing: No such file or directory
configure: WARNING: 'missing' script is too old or missing
diagnostic.cc: In function ‘void fancy_abort(const char*, int, const char*)’:
diagnostic.cc:2027:52: warning: format not a string literal and no format arguments [-Wformat-security]
2027 | fnotice (stderr, diagnostic_kind_text[DK_ICE]);
| ^
gcc.cc: In function ‘long unsigned int get_random_number()’:
gcc.cc:10577:12: warning: ignoring return value of ‘ssize_t read(int, void*, size_t)’, declared with attribute warn_unused_result [-Wunused-result]
10577 | read (fd, &ret, sizeof (HOST_WIDE_INT));
| ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gcc.cc: In function ‘void do_report_bug(const char**, int, char**, char**)’:
gcc.cc:7789:9: warning: ignoring return value of ‘ssize_t write(int, const void*, size_t)’, declared with attribute warn_unused_result [-Wunused-result]
7789 | write (fd, "\n//", 3);
| ~~~~~~^~~~~~~~~~~~~~~
gcc.cc:7792:13: warning: ignoring return value of ‘ssize_t write(int, const void*, size_t)’, declared with attribute warn_unused_result [-Wunused-result]
7792 | write (fd, " ", 1);
| ~~~~~~^~~~~~~~~~~~
gcc.cc:7793:13: warning: ignoring return value of ‘ssize_t write(int, const void*, size_t)’, declared with attribute warn_unused_result [-Wunused-result]
7793 | write (fd, new_argv[i], strlen (new_argv[i]));
| ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gcc.cc:7795:9: warning: ignoring return value of ‘ssize_t write(int, const void*, size_t)’, declared with attribute warn_unused_result [-Wunused-result]
7795 | write (fd, "\n\n", 2);
| ~~~~~~^~~~~~~~~~~~~~~
SDCC.y:77.1-7: warning: POSIX Yacc does not support %expect [-Wyacc]
77 | %expect 3
| ^~~~~~~
In file included from /usr/include/string.h:495,
from SDCC.y:30:
In function ‘strncpy’,
inlined from ‘yyparse’ at SDCC.y:2188:21:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:106:10: warning: ‘__builtin_strncpy’ output may be truncated copying 252 bytes from a string of length 256 [-Wstringop-truncation]
106 | return __builtin___strncpy_chk (__dest, __src, __len, __bos (__dest));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In function ‘strncpy’,
inlined from ‘yyparse’ at SDCC.y:2160:21:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:106:10: warning: ‘__builtin_strncpy’ output may be truncated copying 252 bytes from a string of length 256 [-Wstringop-truncation]
106 | return __builtin___strncpy_chk (__dest, __src, __len, __bos (__dest));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SDCCval.c: In function ‘ulFromVal’:
SDCCval.c:2002:22: warning: passing argument 1 of ‘ullFromVal’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
2002 | return ullFromVal (val);
| ^~~
In file included from SDCCsymt.h:657,
from SDCCast.h:29,
from common.h:43,
from SDCCval.c:26:
SDCCval.h:133:32: note: expected ‘value *’ {aka ‘struct value *’} but argument is of type ‘const value *’ {aka ‘const struct value *’}
133 | unsigned long long ullFromVal (value *);
| ^~~~~~~
gen.c: In function ‘opInfo’:
gen.c:330:23: warning: ‘%s’ directive output may be truncated writing up to 768 bytes into a region of size 60 [-Wformat-truncation=]
330 | snprintf(str, 64, "SYM:%s(%s:%d)",
| ^~~~~~~~~~~~~~~
gen.c:330:23: note: directive argument in the range [-32768, 32767]
In file included from /usr/include/stdio.h:867,
from ./../common.h:21,
from ralloc.h:29,
from gen.c:29:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:67:10: note: ‘__builtin___snprintf_chk’ output between 9 and 782 bytes into a destination of size 64
67 | return __builtin___snprintf_chk (__s, __n, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68 | __bos (__s), __fmt, __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/string.h:495,
from aslink.h:35,
from lklist.c:34:
In function ‘strncpy’,
inlined from ‘lkalist’ at lklist.c:985:9:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:106:10: warning: ‘__builtin_strncpy’ output may be truncated copying between 4 and 11 bytes from a string of length 15 [-Wstringop-truncation]
106 | return __builtin___strncpy_chk (__dest, __src, __len, __bos (__dest));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In function ‘strncpy’,
inlined from ‘lkglist’ at lklist.c:1264:9:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:106:10: warning: ‘__builtin_strncpy’ output may be truncated copying between 3 and 4 bytes from a string of length 15 [-Wstringop-truncation]
106 | return __builtin___strncpy_chk (__dest, __src, __len, __bos (__dest));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In function ‘strncpy’,
inlined from ‘lkglist’ at lklist.c:1274:25:
/usr/include/x86_64-linux-gnu/bits/string_fortified.h:106:10: warning: ‘__builtin_strncpy’ output may be truncated copying between 4 and 11 bytes from a string of length 15 [-Wstringop-truncation]
106 | return __builtin___strncpy_chk (__dest, __src, __len, __bos (__dest));
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lkmem.c: In function ‘summary’:
lkmem.c:366:37: warning: ‘%s’ directive writing up to 4095 bytes into a region of size 127 [-Wformat-overflow=]
366 | sprintf(buff, "'%s'\n", Ram[j].Name);
| ^~
In file included from /usr/include/stdio.h:867,
from lkmem.c:18:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:36:10: note: ‘__builtin___sprintf_chk’ output between 4 and 4099 bytes into a destination of size 128
36 | return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 | __bos (__s), __fmt, __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lkmem.c:372:33: warning: ‘%s’ directive writing up to 4095 bytes into a region of size 127 [-Wformat-overflow=]
372 | sprintf(buff, "'%s'\n", IRam.Name);
| ^~ ~~~~~~~~~
In file included from /usr/include/stdio.h:867,
from lkmem.c:18:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:36:10: note: ‘__builtin___sprintf_chk’ output between 4 and 4099 bytes into a destination of size 128
36 | return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 | __bos (__s), __fmt, __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lksdcclib.c: In function ‘buildlibraryindex_sdcclib’:
lksdcclib.c:151:37: warning: ‘%s’ directive writing 3 bytes into a region of size between 0 and 4095 [-Wformat-overflow=]
151 | sprintf (buff, "%s%s%c%s", lbnh->path, ModName, FSEPX, LKOBJEXT);
| ^~
In file included from /usr/include/stdio.h:867,
from lk_readnl.h:31,
from lksdcclib.c:36:
/usr/include/x86_64-linux-gnu/bits/stdio2.h:36:10: note: ‘__builtin___sprintf_chk’ output 5 or more bytes (assuming 4100) into a destination of size 4096
36 | return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37 | __bos (__s), __fmt, __va_arg_pack ());
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
_rrulonglong.c:60: warning 151: using generic pointer <null> to initialize iTemp8
_rrslonglong.c:60: warning 151: using generic pointer <null> to initialize iTemp8
_rrulonglong.c:60: warning 151: using generic pointer <null> to initialize iTemp8
_rrslonglong.c:60: warning 151: using generic pointer <null> to initialize iTemp8
make[5]: rcD: Command not found
make[5]: [Makefile:41: ../build/ds400/libds400.lib] Error 127 (ignored)
Makefile:95: warning: overriding recipe for target 'Makefile'
Makefile:69: warning: ignoring old recipe for target 'Makefile'
Makefile:95: warning: overriding recipe for target 'Makefile'
Makefile:69: warning: ignoring old recipe for target 'Makefile'
Makefile:97: warning: overriding recipe for target 'Makefile'
Makefile:71: warning: ignoring old recipe for target 'Makefile'
Makefile:98: warning: overriding recipe for target 'Makefile'
Makefile:72: warning: ignoring old recipe for target 'Makefile'
Makefile:97: warning: overriding recipe for target 'Makefile'
Makefile:71: warning: ignoring old recipe for target 'Makefile'
Makefile:96: warning: overriding recipe for target 'Makefile'
Makefile:70: warning: ignoring old recipe for target 'Makefile'
Makefile:98: warning: overriding recipe for target 'Makefile'
Makefile:72: warning: ignoring old recipe for target 'Makefile'
Dist-cleaning also gives many errors:
$ make distclean > /dev/null
make[1]: [Makefile:2376: local-distclean] Error 1 (ignored)
make[1]: [Makefile:2377: local-distclean] Error 1 (ignored)
make[1]: [Makefile:2378: local-distclean] Error 1 (ignored)
make[1]: [Makefile:2379: local-distclean] Error 1 (ignored)
make[1]: [Makefile:2380: local-distclean] Error 1 (ignored)
Makefile:98: warning: overriding recipe for target 'Makefile'
Makefile:72: warning: ignoring old recipe for target 'Makefile'
Makefile:95: warning: overriding recipe for target 'Makefile'
Makefile:69: warning: ignoring old recipe for target 'Makefile'
Makefile:95: warning: overriding recipe for target 'Makefile'
Makefile:69: warning: ignoring old recipe for target 'Makefile'
Makefile:97: warning: overriding recipe for target 'Makefile'
Makefile:71: warning: ignoring old recipe for target 'Makefile'
Makefile:98: warning: overriding recipe for target 'Makefile'
Makefile:72: warning: ignoring old recipe for target 'Makefile'
Makefile:97: warning: overriding recipe for target 'Makefile'
Makefile:71: warning: ignoring old recipe for target 'Makefile'
Makefile:96: warning: overriding recipe for target 'Makefile'
Makefile:70: warning: ignoring old recipe for target 'Makefile'
Makefile:98: warning: overriding recipe for target 'Makefile'
Makefile:72: warning: ignoring old recipe for target 'Makefile'
Makefile:95: warning: overriding recipe for target 'Makefile'
Makefile:69: warning: ignoring old recipe for target 'Makefile'
Makefile:95: warning: overriding recipe for target 'Makefile'
Makefile:69: warning: ignoring old recipe for target 'Makefile'
Makefile:97: warning: overriding recipe for target 'Makefile'
Makefile:71: warning: ignoring old recipe for target 'Makefile'
Makefile:98: warning: overriding recipe for target 'Makefile'
Makefile:72: warning: ignoring old recipe for target 'Makefile'
Makefile:97: warning: overriding recipe for target 'Makefile'
Makefile:71: warning: ignoring old recipe for target 'Makefile'
Makefile:96: warning: overriding recipe for target 'Makefile'
Makefile:70: warning: ignoring old recipe for target 'Makefile'
/bin/sh: line 0: cd: testsuite: No such file or directory
make[3]: [Makefile:3216: distclean] Error 1 (ignored)
/bin/sh: line 0: cd: testsuite: No such file or directory
make[3]: [Makefile:3217: distclean] Error 1 (ignored)
make[3]: [Makefile:3222: distclean] Error 1 (ignored)
make[1]: [Makefile:1256: local-distclean] Error 1 (ignored)
make[1]: [Makefile:1257: local-distclean] Error 1 (ignored)
make[1]: [Makefile:1258: local-distclean] Error 1 (ignored)
make[1]: [Makefile:1259: local-distclean] Error 1 (ignored)
make[1]: [Makefile:1260: local-distclean] Error 1 (ignored)
Maarten
Could you attach the full make output from e.g.
make &> mlog
?I just installed Ubuntu 20.04 LTS in a qemu VM, then installed svn and make, checked out current sdcc, installed everything
./configure --disable-pic14-port --disbale-pic16-port
asked for, and zlib1g-dev (without whichmake
failed. The build succeeded, and I ran regression tests for 6 ports, all of which report no errors.Last edit: Philipp Klaus Krause 2023-05-04
Ok, so it builds. But with a lot of warnings.
And the same for the regression tests.
There was a time when SDCC used to build quite cleanly and so did the regression tests.
If a regression test is testing what is supposed to give a warning, then suppress it. If the warning is unrelated to the actual test, fix it. Just because we use tests from other sources, doesn't mean we should inherit their flaws.
And running 'make distclean > /dev/null' also still shows odd things.
Attached are the logs.
I have not been able to reproduce this
make distclean
issue on my Debian GNU/Linux testing on amd64 system.After not being able to reproduce it in the svn tree I usually work in, I did a fresh checkout, then
./configure
, then themake distclean &> distcleanlog
then./configure && make -j 16
andmake distclean &> distcleanlog2
. Neither log frommake distclean
shows any errors or the warnings you reported for me.However, the two files
distcleanlog
anddistcleanlog2
are not the same for me. Mydistcleanlog
has lots of warnings like this one (the only part that changes is the name of the source file), whiledistcleanlog2
has none:However seeing these two lines in
device/lib/pic16/configure.ac
It seems someone intentionally removed the subdir-objects option?
Either way I don't really want to touch the pic build infrastructure.
Regarding the warnings when building sdcc: the code quality of sdcc has improved over time. But there are still old bits of code around. 20 years ago, then-current SDCC built with fewer warnings. But SDCC from 20 years ago, if it still builds today, would surely build with far more warnings than current SDCC: GCC and clang developers keep adding new warnings.
P.S.: I do not see the
bit here.
At this point IMO the only big point that hasn't been dealt with or discussed is warnings in regtests. I'll open a feature request for dealing with them.
Regarding that
rcD: Command not found
issue you are seeing on bothmake
andmake distclean
: Can you identify where it comes from? When building SDCC, something called "rcD" is only used as an option to sdar, e.g.:So since you see
rcD: Command not found
, this looks to me like during your build, at some time,$(SDAR)
is empty.P.S.: I can now reproduce the error, by doing
make
directly indevice/lib/ds400
:However, the error does not happen when doing
make
at a higher level, sincedevice/lib/Makefile[.in]
contains:SDAR = $(abs_top_builddir)/bin/sdar
Last edit: Philipp Klaus Krause 2023-05-16
What about the "make[5]: rcD: Command not found" ? It's in both makesdcc.log and makeclean.log.
I know of no rcD command line tool. But we do use "-rcD" when running sdar and if $(SDAR) is empty somewhere this could be interpreted as a command. But what is eating the dash(-) in that case?
edit: was writing this while you posted the above.
Last edit: Maarten Brock 2023-05-16
Another mystery is why this fails for ds400 only, while e.g. z80 is fine. Both apparently call sdar in the same way via $SDAR), and neither sets SDAR in the Makefile itself (unlike ds390, which does).
P.S.: Got it. Apparently all ports except for ds400, ds390 and mcs51 use
include $(srcdir)/../incl.mk
, but only ds390 and mcs51 haveSDAR = $(abs_top_builddir)/bin/sdar
inMakefile.in
.So for ds400 only,
$(SDAR)
is empty when doing amake
inlib/ds400
.P.P.S.: Fixed in [r14062]. But I still don't know how you ran into that error during SDCC build or distclean, since I could only reproduce it by using
make
directly indevice/lib/ds400
.Related
Commit: [r14062]
Last edit: Philipp Klaus Krause 2023-05-16
Ah, make is eating the dash interpreting it as to ignore the exit status of the command.
And in this case SDAR is indeed empty.
The warning about strncpy in sdcdb.c is kind of bogus, as it allocates the exact amount right before, but ...
readCdb() seems to allocate 1 char too few for currl->line.
And it also seems to copy 1 char too few from bp into currl->line.
Is this intentional?
Edit: seems to be there since the very beginning ;-(
I'm going to assume that this is a bug.
Last edit: Maarten Brock 2023-05-16
AFAIK you are the one most familiar with sdcdb, so I suggest you decide.
Why is there an "%expect 3" in SDCC.y ?
This appears to be a bison specific construct which is not supported by the 'bison -y' that we're using and neither by the original yacc which we use if there is no bison on the system.
It seems like it is supported by bison -y somewhat. While bison -y will print a warning about it, it will not print warnings for the (now expected) 3 conflicts.
When I worked on the grammar in summer, I was able to reduce the number of conflicts, but not eliminate all of them. So I changed that line down to 3 then.
This just effectively replaces one warning with another one.
vs
May I suggest to just output the real warning and remove the %expect 3 ?
The %expect protects us from unnoticed regressions, though: With the %expect, we get an error if the number of conflicts is anything other than 3.