Tcl Source Code

View Ticket
Login
Ticket UUID: 1e48483c8b3b2702b981ebcc85474512bb3d6e0d
Title: Use of non-standard C code in TCLBOOLWARNING
Type: Bug Version: 9.0b2
Submitter: gustafn3 Created on: 2024-04-25 17:31:54
Subsystem: 10. Objects Assigned To: jan.nijtmans
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2024-05-03 13:06:46
Resolution: Fixed Closed By: gustafn3
    Closed on: 2024-05-03 13:06:46
Description:
The current version of Tcl9 uses the GNU statement expression extension inside 
Tcl_GetBooleanFromObj(), actually in TCLBOOLWARNING

   tclsock.c:1321:22: warning: use of GNU statement expression extension from macro expansion [-Wgnu-statement-expression-from-macro-expansion]
               result = Tcl_GetBooleanFromObj(interp, objPtr, &ok);
                        ^
   /usr/local/ns9/include/tclDecls.h:4056:3: note: expanded from macro 'Tcl_GetBooleanFromObj'
           (TCLBOOLWARNING(boolPtr)(sizeof(*(boolPtr)) >= sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? Tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr)) : \
            ^
   /usr/local/ns9/include/tclDecls.h:4037:37: note: expanded from macro 'TCLBOOLWARNING'
   #   define TCLBOOLWARNING(boolPtr) ({__attribute__((unused)) char _bool_Var[sizeof(*(boolPtr)) > sizeof(int) ? -1 : 1];}),
      
A "statement expressions" is an extension of the GNU C compiler and allows you
to execute a group of statements, returning the value of the last statement. 
This will cause problems on standard C compilers not supporting this extension
User Comments: gustafn3 added on 2024-05-03 13:06:46:
NaviServer compiles now cleanly, but yes, there are still warnings from 
other optional packages used in this compilation run which should be fixed.

In previous iteration of tcl9, there was an error caused by TCLBOOLWARNING:

adpcmds.c: In function ‘NsTclAdpCtlObjCmd’:
/usr/local/ns/include/tclDecls.h:4036:62: error: expected specifier-qualifier-list before ‘extern’
 4036 | #       define TCLBOOLWARNING(boolPtr) (void)(sizeof(struct {_Static_assert(sizeof(*(boolPtr)) <= sizeof(int), "sizeof(boolPtr) too large");int dummy;})),
      |                                                              ^~~~~~~~~~~~~~
/usr/local/ns/include/tclDecls.h:4058:10: note: in expansion of macro ‘TCLBOOLWARNING’
 4058 |         (TCLBOOLWARNING(boolPtr)(sizeof(*(boolPtr)) >= sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? Tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr)) : \
      |          ^~~~~~~~~~~~~~
adpcmds.c:318:25: note: in expansion of macro ‘Tcl_GetBooleanFromObj’
  318 |                     if (Tcl_GetBooleanFromObj(interp, objv[2], &boolVal) != TCL_OK) {
      |                         ^~~~~~~~~~~~~~~~~~~~~
make[1]: *** [<builtin>: adpcmds.o] Error 1
make[1]: *** Waiting for unfinished jobs....
make[1]: Leaving directory '/home/runner/work/naviserver/naviserver/nsd'
make: *** [Makefile:34: all] Error 1
Error: Process completed with exit code 2.

https://github.com/naviserver-project/naviserver/actions/runs/8894601239/job/24436196262

jan.nijtmans added on 2024-05-03 12:38:44:

> The workflow run is now fixed by this:

Not completely ... You didn't adapt the TIP #692 change yet: alias_oc should be changed to Tcl_Size type ;-)

./generic/nsf.c: In function ‘GetClassFromObj’:
./generic/nsf.c:2244:64: warning: passing argument 5 of ‘tclStubsPtr->tcl_GetAliasObj’ from incompatible pointer type [-Wincompatible-pointer-types]
 2244 |                                &alias_interp, &alias_cmd_name, &alias_oc, &alias_ov);
      |                                                                ^~~~~~~~~
      |                                                                |
      |                                                                int *
./generic/nsf.c:2244:64: note: expected ‘Tcl_Size *’ {aka ‘long int *’} but argument is of type ‘int *’

There are more warnings in tcldom and tclexpat, which are not adapted to Tcl 9. yet. Workaround: compile with CFLAGS=-DTCL_8_API


gustafn3 added on 2024-05-03 12:17:27:
Great! The workflow run is now fixed by this:
https://github.com/naviserver-project/naviserver/actions/runs/8894601239/job/24544555992

jan.nijtmans added on 2024-05-03 11:56:51:

Now merged to core-8-branch and trunk.

Thanks for the report!


gustafn3 added on 2024-05-02 16:51:34:
I can confirm that the code tagged with "bug-1e48483c8b" works without complaints. Many thanks for your patience!

jan.nijtmans added on 2024-05-02 15:03:51:

Somebody already found the answer:

https://stackoverflow.com/questions/8656175/how-to-check-that-c99-is-used-and-not-gnu99


jan.nijtmans added on 2024-05-02 09:15:13:

Would [edf316533a466b98|checking for __STRICT_ANSI__] work as well?


gustafn3 added on 2024-05-02 09:10:22:
The second one gets rid of the strange warning message in case the code is
compiled with -std=c99. The goal is a clean compile with paranoia settings. 

One could try to deactivate the warning for this use case (requires pragma PUSH/POP of options), but this adds just more complexity for a job, a compiler should do.

jan.nijtmans added on 2024-05-02 08:44:58:

Another attempth [10f1ca98d0145174|here]

I don't think the second check is correct: we want to check here whether the gnu extensions are enabled or not, __STDC_VERSION__ doesn't really help there. The result is now that using -std=c99 actually disables all compile-time checks. We can put in an additional runtime-check as well (which I did now) for this case.

Any idea to check whether the GNU extensions are enabled or not?


anonymous added on 2024-05-01 17:57:22:
BAWT compiles fine with Gustaf's new macro version on my 7 different Linux test environments. 
The old macro (as contained in latest b2) generated errors when compiling tclMuPdf on Debian 12.

Paul

gustafn3 added on 2024-05-01 10:19:46:
With the following version of the macro, it compiles clean with and without -std=c99 under Linux (tested with gcc version 12.2.0) and macOS (tested with standard Apple compiler clang-1500.3.9.4 and clang 17.0.6)

#if !defined(__cplusplus) && !defined(BUILD_tcl) && !defined(BUILD_tk) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
#       define TCLBOOLWARNING(boolPtr) (void)(sizeof(struct {_Static_assert(sizeof(*(boolPtr)) <= sizeof(int), "sizeof(boolPtr) too large");int dummy;})),
#elif defined(__GNUC__) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
        /* If this gives: "error: size of array ‘_bool_Var’ is negative", it means that sizeof(*boolPtr)>sizeof(int), which is not allowed */
#   define TCLBOOLWARNING(boolPtr) ({__attribute__((unused)) char _bool_Var[sizeof(*(boolPtr)) > sizeof(int) ? -1 : 1];}),
#else
#   define TCLBOOLWARNING(boolPtr)
#endif

jan.nijtmans added on 2024-04-30 14:07:40:

Starting with Tcl/Tk 8.7, we require C99 as a minimum.


gustafn3 added on 2024-04-30 14:07:03:
One can improve the situation when adding "&& __STDC_VERSION__ > 199901L" 
appended to the first two clauses of the TCLBOOLWARNING macro.

This works without any warnings on recent macOS with and without e.g. -std=c99 (tested with standard macOS compiler and e.g. clang-mp-17).

Compiling with gcc (gcc-mp-12 or gcc-mp-13) leads to an error, but looks to me as a mess-up in the interplay of mac-ports if the newest MacOSX.sdk.

jan.nijtmans added on 2024-04-30 14:05:46:

Something like [27e88ccf5a512874|this]?

I guess that the original -Wgnu-statement-expression-from-macro-expansion will be back then, unless you switch from -std=c99 to -std=c11


gustafn3 added on 2024-04-30 13:30:24:
I found an answer to my last question.

Tcl/Tk does require an ANSI C C89 compatible compiler.
https://www.tcl.tk/software/tcltk/platforms.html

Seems as if some more guards are required.

gustafn3 added on 2024-04-30 13:26:21:
... when -std=c99 is used. What is the minimal required C version for Tcl?

gustafn3 added on 2024-04-30 13:22:57:
Unfortunately, the newest version generates an error (with gcc-11 and gcc-12)

https://github.com/naviserver-project/naviserver/actions/runs/8894601239/job/24423198441

gustafn3 added on 2024-04-29 13:04:36:
I've lost my access to a Windows machine, so I have to rely on the docs...

jan.nijtmans added on 2024-04-29 12:07:06:

> See documentation ...

Unfortunately, documentation doesn't always tell the whole story. Did you try it? I tried it with VS2017:

tclZlib.c(474): error C2059: syntax error: 'static_assert'
...

Most likely, for this feature to work we need Visual Studio 2019 version 16.8 Preview 3 (which added more C11 features), and possibly switches like /std:c11, or installing a special SDK. Conclusion: it's too early to depend on that or make any assumptions. Tcl 8.7/9.0 will support VS2015+


gustafn3 added on 2024-04-29 07:22:28:
One should use with MSVC 2015+ "static_assert" and not '_Static_assert'. See documentation:

https://learn.microsoft.com/de-de/cpp/cpp/static-assert?view=msvc-140

jan.nijtmans added on 2024-04-27 17:15:34:

With Visual Studio 2022, this construct gives:

D:\a\tcl\tcl\win\..\generic\tclTest.c(2676): error C2061: syntax error: identifier '_Static_assert'
...

I don't know if that's fixed in later Visual Studio versions, but Tcl/Tk currently supports VS2015+


jan.nijtmans added on 2024-04-26 16:37:02:

Now merged to core-8-branch and trunk


gustafn3 added on 2024-04-26 12:36:36:
yep, this works nicely, no warnings etc.

jan.nijtmans added on 2024-04-26 12:29:59:

Another attempt: [c0bdbd9536f2b001|here]


gustafn3 added on 2024-04-26 12:07:39:
> does this work for you

... mostly, parentheses should be added

tclsock.c:1333:22: warning: operator '?:' has lower precedence than '+'; '+' will be evaluated first [-Wparentheses]
            result = Tcl_GetBooleanFromObj(interp, objPtr, &ok);
                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/ns9/include/tclDecls.h:4057:90: note: expanded from macro 'Tcl_GetBooleanFromObj'
        (TCLBOOLWARNING(boolPtr)(sizeof(*(boolPtr)) >= sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? Tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr)) : \
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
tclsock.c:1333:22: note: place parentheses around the '+' expression to silence this warning
            result = Tcl_GetBooleanFromObj(interp, objPtr, &ok);
                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/ns9/include/tclDecls.h:4057:90: note: expanded from macro 'Tcl_GetBooleanFromObj'
        (TCLBOOLWARNING(boolPtr)(sizeof(*(boolPtr)) >= sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? Tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr)) : \
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^
tclsock.c:1333:22: note: place parentheses around the '?:' expression to evaluate it first
            result = Tcl_GetBooleanFromObj(interp, objPtr, &ok);
                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/local/ns9/include/tclDecls.h:4057:90: note: expanded from macro 'Tcl_GetBooleanFromObj'
        (TCLBOOLWARNING(boolPtr)(sizeof(*(boolPtr)) >= sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? Tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr)) : \

gustafn3 added on 2024-04-26 12:00:15:
There are various means to address C++ compatibility:

one can deactivate the check in c++ (there is anyhow no full coverage), this would address both cases, or

... one can add "-std=c++11" to the compile flags for c++ programs (for the first case)
... one can use the comma operator and a lambda-expression in the CPP case (([]{static_assert(true,"");}, ....)) ... this addresses the second case for c++

Still wondering: wouldn't it be better to reduce casting and all this manual checking, 
and let the compiler spit out warnings on type mismatches (maybe dependent on the "backward compatibility" flag)?

jan.nijtmans added on 2024-04-26 11:35:29:

Does [066670e43164ede9|this] work for you?


jan.nijtmans added on 2024-04-26 11:32:55:

Thanks for the suggestion. I tried it:

warning: identifier ‘static_assert’ conflicts with C++ keyword [-Wc++-compat]
 2787 |                 if (Tcl_GetBooleanFromObj(interp, objv[1], &debugType)+ (int)(sizeof(struct {static_assert(sizeof(debugType) <= sizeof(int),"all is lost");int dummy;})*0)
      |                                                                                              ^~~~~~~~~~~~~
warning: defining type in ‘sizeof’ expression is invalid in C++ [-Wc++-compat]
 2787 |                 if (Tcl_GetBooleanFromObj(interp, objv[1], &debugType)+ (int)(sizeof(struct {static_assert(sizeof(debugType) <= sizeof(int),"all is lost");int dummy;})*0)
      |                                                                                      ^~~~~~

So, I changed it to _Static_assert(), that fixed the first warning but not the second. Conclusion: Your suggestion won't work when tcl.h is included in a C++ project.


gustafn3 added on 2024-04-26 09:51:04:
As i read this, the intention is to issue an error, when someone passes in a value larger than "int" as result pointer in

   Tcl_GetBooleanFromObj(interp, objv[0], boolPtr);

since there is later a cast 

   Tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr))

For the code

   {
       size_t large = 0;
       result = Tcl_GetBooleanFromObj(interp, objPtr, &large);
   }

the error message in this case will be

   error: '_bool_Var' declared as an array with a negative size

as also the comment documents.

This is not great for the following reasons:
1) strange error message
2) generated only on GNU compatible compilers
3) handles just a particular case (object for the result is too large, why not covering too small?)
4) raises a warning also in cases, where everything is fine
   (when the paranoia flags are turned on, probably many developers will do this, when moving to tcl9)

Why not using

   static_assert( constant-expression, string-literal )

when available. It is part of
- C11
- MS: VS 2015
- GCC since gcc-4.6

With some mental contortion, one can also sneak it into expressions, 
as the following snippet shows.

   {
       size_t large = 0;
       result = Tcl_GetBooleanFromObj(interp, objPtr, &large)
               + (int)(sizeof(struct {static_assert(sizeof(large) <= sizeof(int),"all is lost");int dummy;})*0) ;
   }

This would address (1) and (4) and improves the situation on (2) as well.

jan.nijtmans added on 2024-04-25 19:05:47:

TCLBOOLWARNING is meant to produce a compiler error when sizeof(*(boolPtr)) > sizeof(int). There's an #ifdef __GNUC__ around it, so it isn't actually an error.

I didn't find a better way to do this. If you have an idea, please let me known.