// bsla_nullterminated.h -*-C++-*- #ifndef INCLUDED_BSLA_NULLTERMINATED #define INCLUDED_BSLA_NULLTERMINATED #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide macros for use with 'NULL'-terminated variadic functions. // //@MACROS: // BSLA_NULLTERMINATED: warn if last argument is not 'NULL' // BSLA_NULLTERMINATEDAT(ARG_IDX): warn if argument at 'ARG_IDX' is not 'NULL' // BSLA_NULLTERMINATED_IS_ACTIVE: 1 if 'BSLA_NULLTERMINATED' is active // BSLA_NULLTERMINATEDAT_IS_ACTIVE: 1 if 'BSLA_NULLTERMINATEDAT' is active // //@SEE_ALSO: bsla_annotations // //@DESCRIPTION: This component provides preprocessor macros to indicate that a // variadic function's arguments are terminated by 'NULL', or, in the case of // 'BSLA_NULLTERMINATEDAT', by 'NULL' at a certain index. Note that the // terminating 'NULL' must actually be 'NULL' or, with C++11, 'nullptr'; // passing 0 in its place will result in a warning. // ///Macro Reference ///--------------- //: 'BSLA_NULLTERMINATED' //: This annotation on a variadic macro indicates that a warning should be //: issued unless the last argument to the function is explicitly 'NULL'. // //: 'BSLA_NULLTERMINATEDAT(ARG_IDX)' //: This annotation on a variadic function indicates that a warning should //: be issued unless the argument at 'ARG_IDX' is 'NULL', where 'ARG_IDX' //: is the number of arguments from the last, the last argument having //: 'ARG_IDX == 0'. Thus, 'BSLA_NULLTERMINATED' is equivalent to //: 'BSLA_NULLTERMINATEDAT(0)'. // //: 'BSLA_NULLTERMINATED_IS_ACTIVE' //: 'BSLA_NULLTERMINATEDAT_IS_ACTIVE' //: In these two cases, 'X_IS_ACTIVE' is defined to 0 if 'X' expands to //: nothing and 1 otherwise. // ///Usage ///----- // This section illustrates intended use of this component. // ///Example 1: 'catStrings' Function /// - - - - - - - - - - - - - - - - // Suppose we want to have a function that, passed a variable length argument // list of 'const char *' strings terminated by 'NULL', concatenates the // strings, separated by spaces, into a buffer. // // First, we declare and define the function, annotated with // 'BSLA_NULL_TERMINATED': //.. // void catStrings(char *outputBuffer, ...) BSLA_NULLTERMINATED; // void catStrings(char *outputBuffer, ...) // // The specified 'outputBuffer' is a buffer where the output of this // // function is placed. The specified '...' is a 'NULL'-terminated list // // of 'const char *' strings, which are to be copied into // // 'outputBuffer', concatenated together and separated by spaces. The // // behavior is undefined unless the '...' is a 'NULL'-terminated list // // of 'const char *' arguments. // { // *outputBuffer = 0; // // va_list ap; // va_start(ap, outputBuffer); // const char *next; // for (bool first = 1; (next = va_arg(ap, const char *)); first = 0) { // ::strcat(outputBuffer, first ? "" : " "); // ::strcat(outputBuffer, next); // } // va_end(ap); // } //.. // Then, in 'main', we call 'catStrings' correctly: //.. // char buf[1000]; // catStrings(buf, "Now", "you", "see", "it.", NULL); // printf("%s\n", buf); //.. // which compiles without a warning and produces the output: //.. // Now you see it. //.. // Now, we call 'catStrings" again and forget to add the terminating 'NULL': //.. // catStrings(buf, "Now", "you", "don't."); // printf("%s\n", buf); //.. // Finally, we get the compiler warning: //.. // .../bsla_nullterminated.t.cpp:412:47: warning: missing sentinel in function // call [-Wsentinel] // catStrings(buf, "Now", "you", "don't."); // ^ // , nullptr // .../bsla_nullterminated.t.cpp:137:10: note: function has been explicitly // marked sentinel here // void catStrings(char *outputBuffer, ...) // ^ //.. // ///Example 2: 'catVerdict' Function /// - - - - - - - - - - - - - - - - // Suppose we want to have a function that, passed a variable length argument // list of 'const char *' strings terminated by 'NULL', concatenates the // strings, separated by spaces, into a buffer, and then there's an additional // integer argument, interpreted as a boolean, that determines what is to be // appended to the end of the buffer. // // First, we declare and define the function, annotated with // 'BSLA_NULL_TERMINATEDAT(1)': //.. // void catVerdict(char *outputBuffer, ...) BSLA_NULLTERMINATEDAT(1); // void catVerdict(char *outputBuffer, ...) // // The specified 'outputBuffer' is a buffer where output is to be // // placed. All but the last 2 of the specified '...' arguments are // // 'const char *' strings to be concatenated together into // // 'outputBuffer', separated by spaces. The second-to-last argument is // // to be 'NULL', and the last argument is an 'int' interpreted as a // // boolean to determine whether the buffer is to end with a verdict of // // "guilty" or "not guilty". The behavior is undefined unless the // // types of all the arguments are correct and the second to last // // argument is 'NULL'. // { // *outputBuffer = 0; // // va_list ap; // va_start(ap, outputBuffer); // const char *next; // for (bool first = 1; (next = va_arg(ap, const char *)); first = 0) { // ::strcat(outputBuffer, first ? "" : " "); // ::strcat(outputBuffer, next); // } // // const bool guilty = va_arg(ap, int); // ::strcat(outputBuffer, guilty ? ": guilty" : ": not guilty"); // va_end(ap); // } //.. // Then, in 'main', we call 'catVerdict' correctly: //.. // char buf[1000]; // catVerdict(buf, "We find the", "defendant,", "Bugs Bunny", NULL, 0); // printf("%s\n", buf); //.. // which compiles without a warning and produces the output: //.. // We find the defendant, Bugs Bunny: not guilty //.. // Next, we call 'catVerdict' with no 'NULL' passed, and get a warning (and // probably a core dump if we ran it): //.. // catVerdict(buf, "We find the", "defendant,", "Wile E. Coyote", 1); // printf("%s\n", buf); //.. // And we get the following compiler warning: //.. // .../bsla_nullterminated.t.cpp:447:70: warning: missing sentinel in function // call [-Wsentinel] // catVerdict(buf, "We find the", "defendant,", "Wile E. Coyote", 1); // ^ // , nullptr // .../bsla_nullterminated.t.cpp:171:10: note: function has been explicitly // marked sentinel here // void catVerdict(char *outputBuffer, ...) // ^ //.. // Now, we call 'catVerdict' and forget to put the integer that indicates guilt // or innocence after the 'NULL'. This means that 'NULL' is happening at index // 0, not index 1, which violates the requirement imposed by the annotation: //.. // catVerdict(buf, "We find the", "defendant,", "Road Runner", NULL); // printf("%s\n", buf); //.. // Finally, we get the compiler warning: //.. // .../bsla_nullterminated.t.cpp:471:67: warning: missing sentinel in function // call [-Wsentinel] // catVerdict(buf, "We find the", "defendant,", "Road Runner", NULL); // ^ // , nullptr // .../bsla_nullterminated.t.cpp:171:10: note: function has been explicitly // marked sentinel here // void catVerdict(char *outputBuffer, ...) // ^ //.. #include <bsls_platform.h> #if (defined(BSLS_PLATFORM_CMP_GNU) || defined(BSLS_PLATFORM_CMP_CLANG)) && \ !defined(BSLS_PLATFORM_OS_SOLARIS) #define BSLA_NULLTERMINATED __attribute__((__sentinel__)) #define BSLA_NULLTERMINATEDAT(ARG_IDX) \ __attribute__((__sentinel__(ARG_IDX))) #define BSLA_NULLTERMINATED_IS_ACTIVE 1 #define BSLA_NULLTERMINATEDAT_IS_ACTIVE 1 #else #define BSLA_NULLTERMINATED #define BSLA_NULLTERMINATEDAT(ARG_IDX) #define BSLA_NULLTERMINATED_IS_ACTIVE 0 #define BSLA_NULLTERMINATEDAT_IS_ACTIVE 0 #endif #endif // ---------------------------------------------------------------------------- // Copyright 2019 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------