Skip to content

C09-C13 coverage, issue fixes, -42 forbidden-function check - #44

Open
skyreks00 wants to merge 1 commit into
k11q:mainfrom
skyreks00:c09-c13-coverage-and-fixes
Open

skyreks00 wants to merge 1 commit into
k11q:mainfrom
skyreks00:c09-c13-coverage-and-fixes

Conversation

@skyreks00

Copy link
Copy Markdown

C09, C10, C11, C12 and C13 had no tests at all before this — 41 exercises across the five modules, each one checked against both a correct reference implementation and a deliberately broken one before being kept.

New coverage

  • C09: libft builds libft.a via the student's own libft_creator.sh script and links a harness against it; Makefile drops a grader-supplied srcs/ + includes/ft.h fixture and drives make/clean/fclean/re; ft_split
  • C10: display_file, cat, tail, hexdump — built with make and driven through popen, like C06's argv exercises. ft_hexdump -C's byte layout was checked against the real system hexdump across a dozen sizes before being baked in as a self-contained reference generator
  • C11 (8), C12 (18), C13 (8) — straight from the official subject PDFs. C12/C13 share one ft_list.h / ft_btree.h in mini-moul/utils/ instead of duplicating the header per exercise

Fixes #11, #15, #16, #18, #27, #28, #29, #30, #31, #33, #35, #40, #41, #42, #43

The two segfault-class bugs

ft_strlcat's test built dest sized to strlen(dest), but the function is entitled to assume dest has size bytes available — which can be a lot bigger than the initial string. A correct implementation could write straight past the buffer.

// before — sized to the initial content, not to size
char dest[strlen(tests[i].dest) + 1];

// after — sized to whichever is bigger
size_t dest_cap = strlen(tests[i].dest) + 1;
if ((size_t)tests[i].size > dest_cap)
    dest_cap = (size_t)tests[i].size;
char dest[dest_cap];

ft_strcat's test had the opposite problem: dest was a fixed array inside a struct literal, which C zero-initializes for you — so a student who forgot to null-terminate their result would still pass, since the missing byte was already zero.

// poison past the initial content so a missing null terminator
// shows up instead of being masked by an already-zero buffer
memset(dest, 'Z', sizeof(dest));
strcpy(dest, tests[i].initial_dest);

Everything else that was actually broken

  • ft_ten_queens_puzzle was still a stub that didn't even compile, and separately expected 92 solutions (the eight-queens answer) instead of 724
  • ft_strcapitalize never tested the subject's own rule that a digit inside a word blocks capitalization of what follows: 123AA123aa, not 123Aa
  • ft_strlcpy never tested size=0, a classic off-by-one
  • ft_atoi only tested plain spaces even though the subject says isspace(3), and separately pinned a specific overflow/underflow result the subject explicitly calls undefined — removed those two cases
  • 0 ** 0 was untested for both power exercises, even though the subject explicitly defines it as 1
  • ft_sqrt never got an INT_MAX case — a naive while (i * i < nb) i++; overflows and loops forever there
  • ft_convert_base never tested nbr="0", which breaks any implementation that divides until zero and never enters the loop for an input already at zero

Harness bugs affecting every exercise, not just the new ones

test.sh's "strict compile" step was compiling its own test file — which already has #include <string.h> etc. — instead of the student's file, so a missing include in the student's own code silently passed:

- cc -Wall -Werror -Wextra -o test1 $(ls $assignment/*.c | head -n 1)
+ cc -std=gnu99 -Wall -Werror -Wextra -c "$source_file" -o moul_check.o

It also had no timeout anywhere around test execution, so one hanging binary (the ft_sqrt case above, for instance) froze the entire run forever. There's now a 10 second timeout around it, and the exit code — which used to always be 0 regardless of pass/fail — now reflects the real result.

mini-moul.sh crashed on every single invocation:

if detect_assignment; then
  cp -R ~/mini-moulinette/mini-moul mini-moul
  run_norminette        # called here...
  ...
run_norminette() {      # ...but only defined down here
  ...

Bash doesn't hoist function definitions, so this never worked. Fixed, plus lowercase folder name support (c00-c13, #18) and excluded mini-moul/ itself from the norminette scan — it doesn't follow the Norm and was never meant to be graded against it.

New: -42 for forbidden functions (#30)

Each exercise's own subject lists which functions are allowed — most say "None". Nothing in this repo checked that a student wasn't just calling printf where only write was allowed. Every exercise's test file now carries that list straight from its subject:

// ALLOWED_FUNCTIONS: malloc, free

strip_c.c blanks out comments and string/char literal contents from the student's source first, so a function name mentioned in a comment or a string doesn't get flagged, and check_forbidden.sh greps what's left for calls to a fixed list of common libc functions not on that exercise's list. A hit marks the exercise -42 and forces the whole run's final score to -42/100, Status: CHEATER — matching the subject's own rule word for word ("Cheaters get -42, and this grade is non-negotiable"). It's grep-based, not a real parser, so it won't catch a function called only through a pointer, but it covers the obvious case.

Validated against two complete, real student piscine repos

Checking every new test against a reference and a broken implementation doesn't catch problems that only show up on real, organically-written code. So I cloned two full C00–C13 student repos and ran the actual mini-moul.sh entry point (not just test.sh) against every module, treating each failure as either a real student bug to leave alone, or a real bug in this harness.

  • test.sh's isolated compile check was a blind cc -c on whatever .c files sat directly in the exercise folder — anyone who organizes their code into srcs/ + includes/ and drives it with their own Makefile failed wholesale. It now prefers make -s when a Makefile exists, treats zero .c files as fine for header-only exercises (C08 asks for a .h and nothing else), and adds -I to the harness's own utils/ directory so a student who doesn't keep a local copy of ft_list.h/ft_btree.h/ft_stock_str.h isn't penalized for it
  • do-op.c (C11/ex05) had the same blind-wildcard build problem in its own compile step, fixed the same way
  • check_forbidden.sh scanned every .c file sitting in the exercise directory, so a student keeping a local copy of an earlier exercise's deliverable in the same folder (so it compiles standalone) got flagged for that file's calls. It now scans only the specific file the exercise's test #includes — or, for C08's header-only exercises, only the header — and falls back to the old wildcard scan solely for the do-op/C10-style programs that genuinely build every .c file in the directory
  • The most serious one: C09/ex01's Makefile test writes its own srcs/ + includes/ fixture over the student's files (the real subject says the grader supplies them) and deletes it afterward. One of the two real repos checks its own srcs//includes/ into git anyway — the very first run against it permanently deleted those files. It now backs up whatever was already there before writing the fixture and restores it afterward:
static void backup_existing(void)
{
    system("mv ../ex01/srcs ../ex01/moul_srcs_backup 2> /dev/null");
    system("mv ../ex01/includes ../ex01/moul_includes_backup 2> /dev/null");
    system("mv ../ex01/libft.a ../ex01/moul_libft_backup.a 2> /dev/null");
}
  • Two smaller test crashes: C07/ex02's ft_ultimate_range test freed an uninitialized pointer when a student's implementation legitimately never wrote through its output parameter, crashing the whole test binary instead of just failing that case; C07/ex01 printed the wrong array index in its failure message (result[i] instead of result[0]) — cosmetic, never affected pass/fail

…function check

C09, C10, C11, C12 and C13 had no tests at all before this, 41 exercises
across the five modules. C09's libft exercise builds libft.a with the
student's own libft_creator.sh script and then links a small harness
against it to actually exercise the five functions, since there is no
fixed source file name to #include directly. The Makefile exercise
works the same way: the subject says the grader supplies srcs/ and
includes/ft.h itself, so the test drops that fixture in before driving
make/clean/fclean/re and checking that .o files land next to their .c,
that clean does not touch libft.a, and so on. C10's four exercises
(display_file, cat, tail, hexdump) are also full programs with no fixed
file name, so they get built with make and driven through popen the
same way C06's argv exercises already were. ft_hexdump -C in particular
needed its exact byte layout checked against the real system hexdump
across a dozen sizes (empty file, exactly 16 bytes, a partial last
line...) before I trusted it enough to bake in as a self-contained
reference generator, since I did not want the test to depend on hexdump
actually being installed wherever it runs. C11, C12 and C13 came
straight from the official subject PDFs; C12 and C13 both need a
canonical t_list/t_btree struct, which now lives once in
mini-moul/utils/ft_list.h and ft_btree.h instead of being copied into
every exercise directory.

While building all that I went through this repo's open GitHub issues
and fixed what I could actually reproduce. A few were pretty bad.
C03/ex05's test built its dest buffer sized to strlen(dest), but
ft_strlcat is entitled to assume dest has `size` bytes available, which
can be a lot bigger than the initial string - a correct implementation
could write past the end of that buffer and segfault or corrupt memory,
which lines up with the segfault reports in k11q#29, k11q#35, k11q#41, k11q#20 and k11q#23.
C03/ex02 had a sneakier version of the same idea: dest was a fixed-size
array inside a struct literal, which C zero-initializes for you, so a
student who forgot to null-terminate their ft_strcat result would still
pass, because the buffer was already zero from the declaration - that
is exactly k11q#11. Fixed it by poisoning the buffer with a non-zero byte
before the call so a real omission actually shows up. C03/ex04 had a
genuine crash risk too: when ft_strstr correctly returns NULL, the old
test passed that straight into printf("%s", ...), which is undefined
behavior and can segfault depending on the libc, k11q#29 again.

C05/ex08 (ft_ten_queens_puzzle) was still a literal "Sorry, test not
implemented yet" stub with a syntax error in it, which is k11q#43's compile
error. Past that, the stub expected 92 solutions, which is the answer
for eight queens, not ten - ten queens has 724. C02/ex12 was the same
kind of stub, referenced in k11q#40. C02/ex09's capitalize test never
exercised the subject's own rule that a word is alphanumeric, meaning a
digit inside a word blocks capitalization of whatever follows it, which
is k11q#31's exact complaint: 123AA should become 123aa, not 123Aa. C02/ex10
never tested size=0 for ft_strlcpy, a classic off-by-one that k11q#42 was
presumably running into. C04/ex03's atoi tests only used plain spaces
for leading whitespace even though the subject explicitly says
isspace(3), and separately asserted a specific result for integer
overflow/underflow that the subject explicitly states is undefined, so
I removed those two cases rather than pin down behavior the subject
itself refuses to define. C05/ex02 and ex03 never tested 0 to the power
0, which the subject explicitly defines as 1 (k11q#27, k11q#28). C05/ex05's
ft_sqrt never got an INT_MAX case, and it turns out a naive
implementation that just increments i while i*i < nb can overflow and
loop forever there, which is k11q#16's timeout complaint. C07/ex04 never
tested nbr="0", a case that trips up any implementation that builds its
output by dividing until the value hits zero and never enters the loop
for an input that is already zero (k11q#15); also fixed the same
NULL-into-printf("%s") issue there on its error path.

None of the above would have shown up cleanly without three harness
bugs I ran into while chasing them, and these affect every exercise,
not just the new modules. test.sh's strict compile check was compiling
the mini-moul test file itself, which already has its own #include
<string.h>, <stdlib.h> and so on, rather than the student's actual
source, so a student missing an #include in their own file would still
pass - the exact complaint in k11q#33. It now compiles the student's file
in isolation with -c so that class of bug cannot hide anymore.
Separately, GCC's C23 default changes what an empty-parens function
pointer like int (*cmp)() means: it used to mean "unspecified
arguments", now it strictly means zero arguments, which broke every C12
exercise that follows the subject's own prototype, since -std was not
pinned anywhere; -std=gnu99 fixes that. And test.sh had no timeout
anywhere around test execution, so a single hanging student binary -
like the ft_sqrt case above - would freeze the whole run forever with
no way out. There is now a 10 second timeout around it, and the exit
code, which used to always be 0 whether the run passed or failed
because of an unrelated printf at the very end, now actually reflects
the result.

mini-moul.sh itself was worse: it crashed on every single invocation,
because run_norminette was called before its own definition, and bash
does not hoist function definitions the way some other languages do.
While fixing that I also added support for lowercase project folder
names (c00 through c13, not just C00-C13, k11q#18) and stopped norminette
from scanning the freshly-copied mini-moul/ folder itself, which does
not follow the Norm and was never supposed to be graded against it -
previously every run buried the actual result under a wall of
irrelevant norm errors about the test harness's own code.

Last, k11q#30 asked for forbidden-function detection, which did not exist
at all before. Every exercise's own subject lists which functions are
allowed - most say "None", some say "write", one or two list something
like "malloc, free" - and this repo had no way to check that a student
was not just calling printf where only write was allowed. Each
exercise's mini-moul test file now carries a one-line "//
ALLOWED_FUNCTIONS: ..." comment copied straight from that exercise's
subject, so the list lives right next to the test it applies to instead
of in a separate file that could drift out of sync. mini-moul/utils/
strip_c.c blanks out comments and the contents of string and char
literals from the student's source first, so a function name mentioned
in a comment or a string does not get flagged by mistake, and
mini-moul/utils/check_forbidden.sh greps what is left for calls to a
fixed list of the usual suspects (printf, malloc, strcpy, and so on)
that are not in that exercise's allowed list. If it finds one, test.sh
marks that exercise -42 and forces the whole run's final score to
-42/100 with Status: CHEATER, exactly what the real subject says should
happen ("Cheaters get -42, and this grade is non-negotiable"). It is a
heuristic built on grep, not a real C parser, so it will not catch a
function called only through a pointer or anything similarly indirect,
but it covers the obvious case people were actually asking about.

On top of that reference/broken-implementation check, I cloned two
complete, real student piscine repos (C00-C13 all filled in) and ran
every module through mini-moul.sh end to end, not just test.sh,
treating every failure as either a real student bug to leave alone or
a real bug in this harness to fix. That surfaced several more genuine
problems. test.sh's isolated compile check was a blind cc -c on
whatever .c files sat directly in the exercise folder, which fell over
for anyone who organizes their code into srcs/+includes/ and drives it
with their own Makefile - C08 and C10 failed wholesale until the check
learned to prefer make -s when a Makefile exists, treat zero .c files
as fine for header-only exercises (C08 asks for a .h and nothing
else), and add -I to the harness's own utils/ directory so a student
who doesn't keep a local copy of ft_list.h/ft_btree.h/ft_stock_str.h
isn't penalized for it. do-op.c (C11/ex05) had the exact same
blind-wildcard problem in its own build step, fixed the same way.
check_forbidden.sh's file scan had a real false positive too: it
scanned every .c file sitting in the exercise directory, so a student
who kept a local copy of an earlier exercise's deliverable in the same
folder (to make it compile standalone) got flagged for that file's
calls, not their own. It now scans only the specific file the
exercise's test #includes, or, for C08's header-only exercises, only
the header, falling back to the old wildcard scan solely for the
do-op/C10-style programs that genuinely build every .c file in the
directory. The most serious one: C09/ex01's Makefile test writes its
own srcs/+includes/ fixture over the student's files (since the real
subject says the grader supplies them) and deletes it afterward - and
it turns out one of the two real repos checks its own srcs/+includes/
into git anyway, so the very first run of this test against it
permanently deleted those files. It now backs up whatever was already
there before writing the fixture and restores it afterward. Two
smaller test crashes came out of the same sweep: C07/ex02's
ft_ultimate_range test freed an uninitialized pointer when the
student's implementation legitimately never wrote through its output
parameter, which crashed the whole test binary instead of just failing
that one case; and C07/ex01 printed the wrong array index in its
failure message (result[i] instead of result[0]), which was
cosmetically confusing but never affected the actual pass/fail logic.

Every new or rewritten test in here was checked against both a correct
reference implementation and a deliberately broken one before I kept
it, including the harness fixes and the forbidden-function detector
itself, then validated end to end against two complete real student
repos.
@gutierreztomaspro-cmd

Copy link
Copy Markdown

Thanks a lot for your work !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Oubli dans la mini moulinette.

2 participants