Build failure with GCC 15 due to -Werror=unterminated-string-initialization

Brahmajit posted 1 patch 1 month, 3 weeks ago
Build failure with GCC 15 due to -Werror=unterminated-string-initialization
Posted by Brahmajit 1 month, 3 weeks ago
I'm building the latest kernel with GCC 15 and got this build failure

fs/qnx6/inode.c: In function ‘qnx6_checkroot’:
fs/qnx6/inode.c:182:41: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
  182 |         static char match_root[2][3] = {".\0\0", "..\0"};
      |                                         ^~~~~~~
fs/qnx6/inode.c:182:50: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
  182 |         static char match_root[2][3] = {".\0\0", "..\0"};
      |                                                  ^~~~~~
	
This is due to GCC 15 now enables
-Werror=unterminated-string-initialization by default.
This is not the only error, there are many such build failures in
various subsystems, some of theme are easy to fix, while some are not.
In this case I was thinking of something like:

--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -179,7 +179,7 @@ static int qnx6_statfs(struct dentry *dentry, struct kstatfs *buf)
  */
 static const char *qnx6_checkroot(struct super_block *s)
 {
-	static char match_root[2][3] = {".\0\0", "..\0"};
+	static char *match_root[][3] = {".\0\0", "..\0"};
 	int i, error = 0;
 	struct qnx6_dir_entry *dir_entry;
 	struct inode *root = d_inode(s->s_root);

I'm not sure if this is the right implementation or not, but I'm always
open to better ideas (or just increasing the dimension of match_root to
[3][4]) and would love to send in a patch.
-- 
Regards,
Brahmajit
Re: Build failure with GCC 15 due to -Werror=unterminated-string-initialization
Posted by Al Viro 1 month, 3 weeks ago
On Thu, Oct 03, 2024 at 02:46:48AM +0530, Brahmajit wrote:
> I'm building the latest kernel with GCC 15 and got this build failure
> 
> fs/qnx6/inode.c: In function ‘qnx6_checkroot’:
> fs/qnx6/inode.c:182:41: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
>   182 |         static char match_root[2][3] = {".\0\0", "..\0"};
>       |                                         ^~~~~~~
> fs/qnx6/inode.c:182:50: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
>   182 |         static char match_root[2][3] = {".\0\0", "..\0"};
>       |                                                  ^~~~~~
> 	
> This is due to GCC 15 now enables
> -Werror=unterminated-string-initialization by default.
> This is not the only error, there are many such build failures in
> various subsystems, some of theme are easy to fix, while some are not.
> In this case I was thinking of something like:
> 
> --- a/fs/qnx6/inode.c
> +++ b/fs/qnx6/inode.c
> @@ -179,7 +179,7 @@ static int qnx6_statfs(struct dentry *dentry, struct kstatfs *buf)
>   */
>  static const char *qnx6_checkroot(struct super_block *s)
>  {
> -	static char match_root[2][3] = {".\0\0", "..\0"};
> +	static char *match_root[][3] = {".\0\0", "..\0"};

Huh?  That makes no sense whatsoever - you get a single-element
array of 3-element arrays of pointers to char.

What you have written is equivalent to
	static char *s1 = ".\0\0";
	static char *s2 = "..\0";
	static char *match_root[1][3] = {[0][0] = s1, [0][1] = s2, [0][2] = NULL};

and match_root[0] is *NOT* a pointer to char anymore.

Just lose the last \0 in each of those string literals...
Re: Build failure with GCC 15 due to -Werror=unterminated-string-initialization
Posted by Al Viro 1 month, 3 weeks ago
On Wed, Oct 02, 2024 at 10:46:56PM +0100, Al Viro wrote:

> Huh?  That makes no sense whatsoever - you get a single-element
> array of 3-element arrays of pointers to char.
> 
> What you have written is equivalent to
> 	static char *s1 = ".\0\0";
> 	static char *s2 = "..\0";
> 	static char *match_root[1][3] = {[0][0] = s1, [0][1] = s2, [0][2] = NULL};
> 
> and match_root[0] is *NOT* a pointer to char anymore.
> 
> Just lose the last \0 in each of those string literals...

... and looking at the actual code using that, just lose the entire
array -
	if (memcmp(dir_entry[0].de_fname, ".", 2) ||
	    memcmp(dir_entry[1].de_fname, "..", 3))
		error = 1;
and be done with that.
Re: Build failure with GCC 15 due to -Werror=unterminated-string-initialization
Posted by Brahmajit 1 month, 3 weeks ago
On 02.10.2024 22:54, Al Viro wrote:
> ... and looking at the actual code using that, just lose the entire
> array -
> 	if (memcmp(dir_entry[0].de_fname, ".", 2) ||
> 	    memcmp(dir_entry[1].de_fname, "..", 3))
> 		error = 1;
> and be done with that.

Hey sorry for the initial bad code, I'm just starting out. With your
recommendation I wrote this, does this look good?

--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -179,8 +179,7 @@ static int qnx6_statfs(struct dentry *dentry, struct kstatfs *buf)
  */
 static const char *qnx6_checkroot(struct super_block *s)
 {
-	static char match_root[2][3] = {".\0\0", "..\0"};
-	int i, error = 0;
+	int error = 0;
 	struct qnx6_dir_entry *dir_entry;
 	struct inode *root = d_inode(s->s_root);
 	struct address_space *mapping = root->i_mapping;
@@ -189,11 +188,9 @@ static const char *qnx6_checkroot(struct super_block *s)
 	if (IS_ERR(folio))
 		return "error reading root directory";
 	dir_entry = kmap_local_folio(folio, 0);
-	for (i = 0; i < 2; i++) {
-		/* maximum 3 bytes - due to match_root limitation */
-		if (strncmp(dir_entry[i].de_fname, match_root[i], 3))
-			error = 1;
-	}
+	if (memcmp(dir_entry[0].de_fname, ".", 2) ||
+	    memcmp(dir_entry[1].de_fname, "..", 3))
+		error = 1;
 	folio_release_kmap(folio, dir_entry);
 	if (error)
 		return "error reading root directory.";

-- 
Regards,
listout
[PATCH 1/1] fs/qnx6: Fix building with GCC 15
Posted by Brahmajit Das 1 month, 3 weeks ago
GCC 15 enables -Werror=unterminated-string-initialization by default.
This results in the following build error

fs/qnx6/inode.c: In function ‘qnx6_checkroot’:
fs/qnx6/inode.c:182:41: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
  182 |         static char match_root[2][3] = {".\0\0", "..\0"};
      |                                         ^~~~~~~
fs/qnx6/inode.c:182:50: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
  182 |         static char match_root[2][3] = {".\0\0", "..\0"};
      |                                                  ^~~~~~

Dropping to match_root array and drictly comparing dir_entry entries via
memcmp provides a work aroud for the build error.

Signed-off-by: Brahmajit Das <brahmajit.xyz@gmail.com>
---
 fs/qnx6/inode.c | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 85925ec0051a..3310d1ad4d0e 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -179,8 +179,7 @@ static int qnx6_statfs(struct dentry *dentry, struct kstatfs *buf)
  */
 static const char *qnx6_checkroot(struct super_block *s)
 {
-	static char match_root[2][3] = {".\0\0", "..\0"};
-	int i, error = 0;
+	int error = 0;
 	struct qnx6_dir_entry *dir_entry;
 	struct inode *root = d_inode(s->s_root);
 	struct address_space *mapping = root->i_mapping;
@@ -189,11 +188,9 @@ static const char *qnx6_checkroot(struct super_block *s)
 	if (IS_ERR(folio))
 		return "error reading root directory";
 	dir_entry = kmap_local_folio(folio, 0);
-	for (i = 0; i < 2; i++) {
-		/* maximum 3 bytes - due to match_root limitation */
-		if (strncmp(dir_entry[i].de_fname, match_root[i], 3))
-			error = 1;
-	}
+	if (memcmp(dir_entry[0].de_fname, ".", 2) ||
+	    memcmp(dir_entry[1].de_fname, "..", 3))
+		error = 1;
 	folio_release_kmap(folio, dir_entry);
 	if (error)
 		return "error reading root directory.";
-- 
2.46.2

Re: [PATCH 1/1] fs/qnx6: Fix building with GCC 15
Posted by Al Viro 1 month, 3 weeks ago
On Fri, Oct 04, 2024 at 03:19:21PM +0530, Brahmajit Das wrote:
> GCC 15 enables -Werror=unterminated-string-initialization by default.
> This results in the following build error
> 
> fs/qnx6/inode.c: In function ‘qnx6_checkroot’:
> fs/qnx6/inode.c:182:41: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
>   182 |         static char match_root[2][3] = {".\0\0", "..\0"};
>       |                                         ^~~~~~~
> fs/qnx6/inode.c:182:50: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
>   182 |         static char match_root[2][3] = {".\0\0", "..\0"};
>       |                                                  ^~~~~~
> 
> Dropping to match_root array and drictly comparing dir_entry entries via
> memcmp provides a work aroud for the build error.

LGTM, except that I'd probably make the commit message less warning-centric -
something like

	qnx6_checkroot() had been using weirdly spelled initializer - it needed
	to initialize 3-element arrays of char and it used NUL-padded 3-character
	string literals (i.e. 4-element initializers, with completely pointless
	zeroes at the end).

	That had been spotted by gcc-15[*]; prior to that gcc quietly dropped
	the 4th element of initializers.

	However, none of that had been needed in the first place - all this array
	is used for is checking that the first directory entry in root directory
	is "." and the second - "..".  The check had been expressed as a loop,
	using that match_root[] array.  Since there is no chance that we ever
	want to extend that list of entries, the entire thing is much too fancy
	for its own good; what we need is just a couple of explicit memcmp()
	and that's it.

	[*] <quoted warnings>

would explain what was really going on - the point is not to make gcc STFU, it's
to make the code more straightforward.  The warning is basically "it smells
somewhat fishy around >here<, might be worth taking a look".  And yes, it turned
out to be fishy; minimal "make it STFU" would be to strip those NULs from
the initializers (i.e. just go for static char match_root[2][3] = {".", ".."}; -
an array initializer is zero-padded if it's shorter than the array), but that
wasn't the only, er, oddity in that code.
RE: [PATCH 1/1] fs/qnx6: Fix building with GCC 15
Posted by David Laight 1 month, 3 weeks ago
...
> would explain what was really going on - the point is not to make gcc STFU, it's
> to make the code more straightforward.  The warning is basically "it smells
> somewhat fishy around >here<, might be worth taking a look".  And yes, it turned
> out to be fishy; minimal "make it STFU" would be to strip those NULs from
> the initializers (i.e. just go for static char match_root[2][3] = {".", ".."}; -
> an array initializer is zero-padded if it's shorter than the array), but that
> wasn't the only, er, oddity in that code.

Indeed - looks like it is checking that the first two directory entries
are "." and ".." in about the most complex way possible.

I have vague recollections on some code that ignored the first two entries
because they 'must be "." and ".."' - and then failed because some filesystem
(and I can't even remember the O/S) didn't meet its expectations!

A simple:
	if (strcmp(dir_entry[0].de_fname, ".") || strcmp(dir_entry[1].de_fname, ".."))
		error = 1;
would suffice.
The compiler ought to completely inline them.
On x86 to:
	error |= *(u16 *)dir_entry[0].de_fname ^ '.';
	error |= (*(u32 *)dir_entry[1].de_fname & 0xffffff) ^ ('.' * 0x101);
but I bet it doesn't!
(and it isn't quite the same)

	David.

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)
Re: [PATCH 1/1] fs/qnx6: Fix building with GCC 15
Posted by Al Viro 1 month, 3 weeks ago
On Sun, Oct 06, 2024 at 07:38:07PM +0000, David Laight wrote:
> ...
> > would explain what was really going on - the point is not to make gcc STFU, it's
> > to make the code more straightforward.  The warning is basically "it smells
> > somewhat fishy around >here<, might be worth taking a look".  And yes, it turned
> > out to be fishy; minimal "make it STFU" would be to strip those NULs from
> > the initializers (i.e. just go for static char match_root[2][3] = {".", ".."}; -
> > an array initializer is zero-padded if it's shorter than the array), but that
> > wasn't the only, er, oddity in that code.
> 
> Indeed - looks like it is checking that the first two directory entries
> are "." and ".." in about the most complex way possible.
> 
> I have vague recollections on some code that ignored the first two entries
> because they 'must be "." and ".."' - and then failed because some filesystem
> (and I can't even remember the O/S) didn't meet its expectations!
> 
> A simple:
> 	if (strcmp(dir_entry[0].de_fname, ".") || strcmp(dir_entry[1].de_fname, ".."))
> 		error = 1;
> would suffice.

memcmp(), please.  strcmp() is _not_ guaranteed to be safe without both being
NUL-terminated; yes, compiler will almost simplify that in case when one of
the arguments is a string literal, but it's better to do straight memcmp() in
this case.  It's not worth trying to be fancy there...
Re: [PATCH 1/1] fs/qnx6: Fix building with GCC 15
Posted by Brahmajit 1 month, 3 weeks ago
On 06.10.2024 21:00, Al Viro wrote:
> > Indeed - looks like it is checking that the first two directory entries
> > are "." and ".." in about the most complex way possible.
> > 
> > I have vague recollections on some code that ignored the first two entries
> > because they 'must be "." and ".."' - and then failed because some filesystem
> > (and I can't even remember the O/S) didn't meet its expectations!
> > 
> > A simple:
> > 	if (strcmp(dir_entry[0].de_fname, ".") || strcmp(dir_entry[1].de_fname, ".."))
> > 		error = 1;
> > would suffice.
> 
> memcmp(), please.  strcmp() is _not_ guaranteed to be safe without both being
> NUL-terminated; yes, compiler will almost simplify that in case when one of
> the arguments is a string literal, but it's better to do straight memcmp() in
> this case.  It's not worth trying to be fancy there...

Hi Al,
Hi David,

Is my version 2 of the patch correct, I updated with Al's initial
recommendation (both in terms of the patch and commit). Is there
anything else that needs to be updated/added.
-- 
Regards,
listout
Re: [PATCH 1/1] fs/qnx6: Fix building with GCC 15
Posted by Brahmajit 1 month, 3 weeks ago
On 04.10.2024 19:44, Al Viro wrote:
> LGTM, except that I'd probably make the commit message less warning-centric -
> something like
> ...
> would explain what was really going on - the point is not to make gcc STFU, it's
> to make the code more straightforward.  The warning is basically "it smells
> somewhat fishy around >here<, might be worth taking a look".  And yes, it turned
> out to be fishy; minimal "make it STFU" would be to strip those NULs from
> the initializers (i.e. just go for static char match_root[2][3] = {".", ".."}; -
> an array initializer is zero-padded if it's shorter than the array), but that
> wasn't the only, er, oddity in that code.

Thank you very much for the feedback, sending the patch with updates
commit shortly.

-- 
Regards,
listout
[PATCH v2 1/1] fs/qnx6: Fix building with GCC 15
Posted by Brahmajit Das 1 month, 3 weeks ago
qnx6_checkroot() had been using weirdly spelled initializer - it needed
to initialize 3-element arrays of char and it used NUL-padded
3-character string literals (i.e. 4-element initializers, with
completely pointless zeroes at the end).

That had been spotted by gcc-15[*]; prior to that gcc quietly dropped
the 4th element of initializers.

However, none of that had been needed in the first place - all this
array is used for is checking that the first directory entry in root
directory is "." and the second - "..".  The check had been expressed as
a loop, using that match_root[] array.  Since there is no chance that we
ever want to extend that list of entries, the entire thing is much too
fancy for its own good; what we need is just a couple of explicit
memcmp() and that's it.

[*]: fs/qnx6/inode.c: In function ‘qnx6_checkroot’:
fs/qnx6/inode.c:182:41: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
  182 |         static char match_root[2][3] = {".\0\0", "..\0"};
      |                                         ^~~~~~~
fs/qnx6/inode.c:182:50: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
  182 |         static char match_root[2][3] = {".\0\0", "..\0"};
      |                                                  ^~~~~~

Signed-off-by: Brahmajit Das <brahmajit.xyz@gmail.com>
---
 fs/qnx6/inode.c | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/fs/qnx6/inode.c b/fs/qnx6/inode.c
index 85925ec0051a..3310d1ad4d0e 100644
--- a/fs/qnx6/inode.c
+++ b/fs/qnx6/inode.c
@@ -179,8 +179,7 @@ static int qnx6_statfs(struct dentry *dentry, struct kstatfs *buf)
  */
 static const char *qnx6_checkroot(struct super_block *s)
 {
-	static char match_root[2][3] = {".\0\0", "..\0"};
-	int i, error = 0;
+	int error = 0;
 	struct qnx6_dir_entry *dir_entry;
 	struct inode *root = d_inode(s->s_root);
 	struct address_space *mapping = root->i_mapping;
@@ -189,11 +188,9 @@ static const char *qnx6_checkroot(struct super_block *s)
 	if (IS_ERR(folio))
 		return "error reading root directory";
 	dir_entry = kmap_local_folio(folio, 0);
-	for (i = 0; i < 2; i++) {
-		/* maximum 3 bytes - due to match_root limitation */
-		if (strncmp(dir_entry[i].de_fname, match_root[i], 3))
-			error = 1;
-	}
+	if (memcmp(dir_entry[0].de_fname, ".", 2) ||
+	    memcmp(dir_entry[1].de_fname, "..", 3))
+		error = 1;
 	folio_release_kmap(folio, dir_entry);
 	if (error)
 		return "error reading root directory.";
-- 
2.46.2

Re: [PATCH v2 1/1] fs/qnx6: Fix building with GCC 15
Posted by Al Viro 1 week, 6 days ago
On Sat, Oct 05, 2024 at 01:21:32AM +0530, Brahmajit Das wrote:
> qnx6_checkroot() had been using weirdly spelled initializer - it needed
> to initialize 3-element arrays of char and it used NUL-padded
> 3-character string literals (i.e. 4-element initializers, with
> completely pointless zeroes at the end).
> 
> That had been spotted by gcc-15[*]; prior to that gcc quietly dropped
> the 4th element of initializers.
> 
> However, none of that had been needed in the first place - all this
> array is used for is checking that the first directory entry in root
> directory is "." and the second - "..".  The check had been expressed as
> a loop, using that match_root[] array.  Since there is no chance that we
> ever want to extend that list of entries, the entire thing is much too
> fancy for its own good; what we need is just a couple of explicit
> memcmp() and that's it.
> 
> [*]: fs/qnx6/inode.c: In function ‘qnx6_checkroot’:
> fs/qnx6/inode.c:182:41: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
>   182 |         static char match_root[2][3] = {".\0\0", "..\0"};
>       |                                         ^~~~~~~
> fs/qnx6/inode.c:182:50: error: initializer-string for array of ‘char’ is too long [-Werror=unterminated-string-initialization]
>   182 |         static char match_root[2][3] = {".\0\0", "..\0"};
>       |                                                  ^~~~~~
> 
> Signed-off-by: Brahmajit Das <brahmajit.xyz@gmail.com>

Acked-by: Al Viro <viro@zeniv.linux.org.uk>

Sorry, hadn't realized it had fallen through the cracks.
Christian, usually you are handling vfs/vfs.git#vfs.misc; I can put
it there myself, if you prefer it that way...