Before using this updated packaging you will need
to do the following (one time):
sh sys/msdos/fetch-cross-compiler.sh
And you'll need to update your Makefiles as follows.
On Linux:
sh sys/msdos/setup.sh sys/unix/hints/linux.370
or on macOS;
sh sys/msdos/setup.sh sys/unix/hints/macOS.370
Create the msdos package with:
make CROSS_TO_MSDOS=1 package
For now, this will prevent the NONNULLxxx arguments from being
defined under a djgpp compiler or crosscompiler.
paxed reported a segfault under msdos:
nethack.exe
Exiting due to signal SIGSEGV
Page fault at eip=000c3f8c, error=0004
eax=00000000 ebx=00000000 ecx=00000000 edx=0000000f esi=00000000 edi=00000001
ebp=00589988 esp=00589970 program=C:\NH370\NETHACK.EXE
cs: sel=00a7 base=00400000 limit=0063ffff
ds: sel=00af base=00400000 limit=0063ffff
es: sel=00af base=00400000 limit=0063ffff
fs: sel=008f base=00001a20 limit=0000ffff
gs: sel=00bf base=00000000 limit=0010ffff
ss: sel=00af base=00400000 limit=0063ffff
App stack: [00589ba8..00389ba8] Exceptn stack: [00389af4..00387bb4]
Call frame traceback EIPs:
0x000c3f8c _read_config_file+19
0x0017619f _initoptions_finish+577
0x00176371 _initoptions+157
0x0025cec4 _pcmain+365
0x0025d8d9 _main+41
He was able to 'git bisect' to the macro definitions change,
and confirmed that the segfault no longer occurs after this commit.
There may be further investigation on this later.
Callgrind showed recalc_mapseen was three times more expensive (in terms
of instructions read) than anything else in our codebase. It was being
called in every vision change, re-evaluating the last seen map terrain
type for every map location in sight.
Remove updating the lastseen info in the vision code, and make a small
change so newsym() uses update_lastseentyp.
From my short tests, this seems to work correctly ...
If tutorial is entered, we get following leak on exit:
=================================================================
==81358==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 96 byte(s) in 3 object(s) allocated from:
#0 0x7f6996edefdf in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x5601c255bcbb in alloc /home/miku/src/NetHack/src/alloc.c:71
Indirect leak of 5064 byte(s) in 3 object(s) allocated from:
#0 0x7f6996edefdf in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x5601c255be1e in alloc /home/miku/src/NetHack/src/alloc.c:71
#2 0x5601c255be1e in dupstr /home/miku/src/NetHack/src/alloc.c:236
SUMMARY: AddressSanitizer: 5160 byte(s) leaked in 6 allocation(s).
Fix this by freeing the cloned selection before returning.
Replace one recenly added 'croom' test with assert(croom != NULL);
keep the other one. Mark fill_ordinary_room() as requiring that its
first argument be non-Null. Check for malformed subroom data before
calling it.
Plus miscellaneous reformatting.
Fix some of the extreme verbosity for null vs non-null triggered
by mklev.c. dungeon_branch() never returns Null.
'#include <assert.h>' should probably be moved out of multiple .c
files and into cstd.h or some such but this doesn't do that.
Checking the callers:
newsym() the use of see_with_infrared() is guarded by
} else if ((mon = m_at(x, y)) != 0 [...]
do_mgivenname() the use of see_with_infrared is guarded by !mtmp:
&& (!mtmp
|| (!sensemon(mtmp)
&& (!(cansee(cx, cy) || see_with_infrared(mtmp))
howmonseen(mon) dereferences mon in other places, so it would
segfault if mon were NULL; howmonseen has NONNULLARG1.
callers were checked:
domove_attackmon_at(mtmp, x, y, displaceu) has mtmp declared nonnull;
there are dereferences of mtmp in the first line of code in
the function.
In domove_core():
The 1st occurrence of is_safemon(mtmp) is guarded by if (mtmp) { }.
The 2nd occurrence of is_safemon(mtmp) is inside an if (mtmp) { } block.
The 3rd occurrence of is_safemon(mtmp) was just remediated by 987be7e8.
In lookaround():
The only occurrence of is_safemon(mtmp) is inside an
if ((mtmp = m_at(x, y)) != 0 [...] { } block.
In do_attack(mtmp), in uhitm.c:
The parameter is declared NONNULLARG1, and the 1st line of
code contains a dereference with mtmp->data, which would
segfault if mtmp were NULL.
Following line 2425 of hack.c, in domove_core():
mtmp = m_at(x, y);
mtmp can be null.
There were two if blocks following that, both of which
only make sense when mtmp is not null.
One of them was explicitly checking for mtmp being non-null,
and the other was avoiding catastrophe by relying on a
hidden check buried within an _is_safepet(mon) macro.
Place both of those blocks into an
if (mtmp) { }
block.
99% of the diff is just indentation.
Checking the callers:
toss_up() would have segfaulted prior to use of stone_missile() if obj were NULL.
thitu() now has a guard prior to use of stone_missile()
ohitmon() would have crashed from earlier dereference otmp->dknown if it were NULL,
otmp arg is declared nonnull
thitm() now has a guard prior to use of stone_missile().
hmon_hitmon_do_hit() null obj takes a different code path than the code path
using stone_missile(); comment asserting that added
The static analyzer complained about use of 'obj' maybe being Null
when used in an impossible warning, but that warning will never
appear for the case where obj is actually Null. Add an assert()
that should let it figure that out, and move the impossible check
inside the 'else' clause where the check matters. (Either of those
by itself ought to be adequate to pacify the analyzer.)
Define some macros in include/tradstdc.h, for compilers that support
__attribute__((nonnull)), to assist in identifying which parameters
on functions are not supposed to be null pointers.
Next, for the majority of functions declared in include/extern.h, this
adds the appropriate macro that matches the actual use of each function's
parameters. The additions were done after performing some analysis.
These were the rules that were followed when determining which function
parameters should be nonnul, and which are nullable:
1. If the first use of, or reference to, the pointer parameter in the
function is a dereference, then the parameter will be considered
nonnull.
2. If there is code in the function that tests for the pointer parameter
being null, and adjusts the code-path accordingly so that no segfault
will occur, then the parameter will not be considered nonnull (it can
be null).
The use of the nonnull attributes allows the compiler to detect code in
callers of the function where a null parameter could get passed to the function.
If a warning is received the developer will have to do one of the following:
- If the null being passed to the function is now appropriate,
and the function should be able to expect a null parameter, then the
NONNULLxxx macro will have to be removed from the function's prototype.
or
- If the null being passed to the function is not appropriate,
correct the caller so it is not passing null.
or
- If the warning is about comparing to null, it may indicate an
unnecessary null check in the code involved. If it is deemed to be
unnecessary, it can then be removed.
Some static analysis tools apparently can work with the attribute, as well.
Following this, it was discovered that some functions were using one of the
(now) nonnull parameters in the first argument to the 'is_art(obj, ART)'
macro, which is defined like so:
=> #define is_art(o,art) ((o) && (o)->oartifact == (art))
That macro expansion inline resulted in a diagnostic warning because of the
'(o)' portion of the expanded macro, anywhere the macro was used with one of
the nonnull parameters. A test against null for a 'nonnull parameter' causes
a diagnostic warning.
To work around that, I replaced the is_art() macro with a function in
artifact.c, that accomplishes the same thing as the macro.
=> boolean
is_art(struct obj *obj, int art)
{
if (obj && obj->oartifact == art)
return TRUE;
return FALSE;
}
Some documentation...
These are the macros that have been defined for use when specifying the nonnull
parameters in a function prototype:
----------------------------------------------------------------------------
| Macro | Purpose |
+----------------+---------------------------------------------------------+
| NONULL | The function return value is never NULL. |
+----------------+---------------------------------------------------------+
| NONNULLPTRS | Every pointer argument is declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG1 | The 1st argument is declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG2 | The 2nd argument is declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG3 | The 3rd argument is declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG4 | The 4th argument is declared nonnull (not used). |
+----------------+---------------------------------------------------------+
| NONNULLARG5 | The 5th argument is declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG7 | The 7th argument is declared nonnull (bhit). |
+----------------+---------------------------------------------------------+
| NONNULLARG12 | The 1st and 2nd arguments are declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG13 | The 1st and 3rd arguments are declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG123 | The 1st, 2nd and 3rd arguments are declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG14 | The 1st and 4th arguments are declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG134 | The 1st, 3rd and 4th arguments are declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG17 | The 1st and 7th arguments are declared nonnull (this |
| | was a special-case added for askchain(), where the |
| | arguments are spread out that way. This macro |
| | could be removed if the askchain arguments in the |
| | prototype and callers were changed to make the |
| | nonnull arguments side-by-side). |
+----------------+---------------------------------------------------------+
| NONNULLARG145 | The 1st, 4th and 5th arguments are declared nonnull |
| | (this was a special-case added for find_roll_to_hit(), |
| | in uhitm.c, where the arguments are spread out that way.|
| | We can't just use NONNULLPTRS there because the 3rd |
| | argument 'weapon' can be NULL). |
+----------------+---------------------------------------------------------+
| NONNULLARG24 | The 2nd and 4th arguments are declared nonnull (this |
| | was a special-case added for query_objlist() |
| | in invent.c). |
+----------------+---------------------------------------------------------+
| NONNULLARG45 | The 4th and 5th arguments are declared nonnull (this |
| | was a special-case added for do_screen_description(), |
| | in pager.c, where the arguments are spread out that |
| | way. We can't just use NONNULLPTRS there because the |
| | 6th argument can be NULL). |
+----------------+---------------------------------------------------------+
| NO_NONNULLS | This macro expands to nothing. It is just used to |
| | mark that analysis has been done on the function, |
| | and concluded that none of the arguments could be |
| | marked nonnull.That distinguishes a function that has |
| | not been analyzed (yet), from one that has. |
+----------------+---------------------------------------------------------+
The NO_NONNULLS macro is meant to place a flag on the prototype to
make people aware that an assessed function was determined to not
be eligible for nonnull parameters. It expands to nothing.
Unfortunately, that macro was added partway through this exercise, so there
aren't many instances of it in the upper parts of include/extern.h, even though
the functions there were likely assessed and categorized as not having any
eligible nonnull parameters. It just never got any macro at all, in that case.
Following the parameter usage analysis that was done, the following was
noted:
Some NetHack functions have added a test to catch a passed null
parameter, and exit the function early as a result, or call
impossible(), and then exit. While that approach prevents segfaults
from dereferencing a null parameter, the early return is silent
(when impossible is not called anyway), and the function's true
purpose is not fulfilled. Also, the calling function may have no
awareness that the function did not complete its intended purpose,
in many instances.
Functions with such a test and early return, cannot have the parameter
declared 'nonnull', because the code to test for 'null' will cause a
diagnostic to be issued if the parameter is nonnull.
It might be good to revisit some of those functions and consider,
on a case by case basis, declaring the parameter nonnull in the
prototype, and the test/code-path commented out.
If a monster picks up an item thrown by the hero, change its 'how_lost'
flag from LOST_THROWN to LOST_STOLEN and pickup_stolen will be used
instead of pickup_thrown to decide whether to override pickup_types
and autopickup exceptions when hero eventually steps on it. If a
monster picks up an item dropped by the hero, change its flag to
LOST_NONE and autopickup will work normally without being overridden
by dropped_nopick. If item is flagged as stolen, leave it that way.
Moving over at item that's resting on ice gives a message about there
being ice present and then about the item, whether mention_decor is On
or Off. With it On, you'll get a message about being back on solid
ground as soon as you leave the ice. With it Off you wouldn't get
that at all if not levitating; that's the basic no-mention_decor
behavior for ice. However, if you were levitating, you would get a
delayed "back on solid ground" message when moving over some other
object, which might occur quite a bit later. Autopickup handling is
calling describe_decor() when the hero is levitating and some of that
wasn't appropriate for no-mention_decor.
This issue has been present since I first implemented mention_decor,
not introduced by recent back_on_ground() changes.
Fuzzer encountered an error because the game couldn't place
a level region; allow placing the stairs to the left or right
edge of the fixed map part, just in case the randomly generated
map didn't extend out of it.
Fix the teleport region preventing falling into the orctown.
selection_getbounds() has a check and early return.
Initialization will ensure a known state if that early return
were ever taken.
This is an alternative approach to pr #1163.
Reverse part of commit 8b5e9eadb1.
Shouldn't use get_obj_location(obj,...) to try to figure out if obj
has been deleted. That routine uses obj->where rather than scanning
the various object lists and doing that when obj has been deleted
would access stale memory.
The following were listed in extern.h as residing in makemon.c,
but they are actually in mon.c:
copy_mextra(struct monst *, struct monst *);
dealloc_mextra(struct monst *);
usmellmon(struct permonst *);