Index: generic/nsf.c =================================================================== diff -u -N -r9797190b64be6005808101d6aff148380c055cb3 -r7e090d5c59743ecf7e8fd8d05d5130c5bce092db --- generic/nsf.c (.../nsf.c) (revision 9797190b64be6005808101d6aff148380c055cb3) +++ generic/nsf.c (.../nsf.c) (revision 7e090d5c59743ecf7e8fd8d05d5130c5bce092db) @@ -3428,19 +3428,19 @@ } else if (methodObj->typePtr == Nsf_OT_tclCmdNameType) { containsSpace = 0; } else { - containsSpace = strchr(methodName, ' ') != NULL; + containsSpace = NsfHasTclSpace(methodName); } if (containsSpace != 0) { - tailContainsSpace = strchr(NSTail(methodName), ' ') != NULL; + tailContainsSpace = NsfHasTclSpace(NSTail(methodName)); } else { tailContainsSpace = 0; } /*fprintf(stderr, "<%s> containsSpace %d tailContainsSpace %d\n", methodName, containsSpace, tailContainsSpace);*/ #if !defined(NDEBUG) if (containsSpace != 0) { - assert(strchr(methodName, ' ') != 0); + assert(NsfHasTclSpace(methodName)); } else { assert(tailContainsSpace == 0); } @@ -16573,6 +16573,11 @@ nonnull_assert(body != NULL); nameStr = ObjStr(nameObj); + + if (*nameStr == '\0' || NsfHasTclSpace(nameStr)) { + return NsfPrintError(interp, "invalid method name '%s'", nameStr); + } + if (precondition != NULL && postcondition == NULL) { return NsfPrintError(interp, "%s method '%s'; when specifying a precondition (%s)" " a postcondition must be specified as well", Index: generic/nsfInt.h =================================================================== diff -u -N -r09b4bca7c8d5c44f6be0b2c04ebfcdb7a58fd5ae -r7e090d5c59743ecf7e8fd8d05d5130c5bce092db --- generic/nsfInt.h (.../nsfInt.h) (revision 09b4bca7c8d5c44f6be0b2c04ebfcdb7a58fd5ae) +++ generic/nsfInt.h (.../nsfInt.h) (revision 7e090d5c59743ecf7e8fd8d05d5130c5bce092db) @@ -1295,4 +1295,11 @@ #define vsnprintf _vsnprintf #endif +/* There are six whitespace characters in Tcl, which serve as element + separators in string representations of Tcl lists. See tclUtil.c */ + +#define NsfHasTclSpace(str) \ + (strpbrk((str), " \t\n\r\v\f") != NULL) + + #endif /* _nsf_int_h_ */ Index: tests/methods.test =================================================================== diff -u -N -r224bb58979f84adf7d1488798a4bfbf84af803ca -r7e090d5c59743ecf7e8fd8d05d5130c5bce092db --- tests/methods.test (.../methods.test) (revision 224bb58979f84adf7d1488798a4bfbf84af803ca) +++ tests/methods.test (.../methods.test) (revision 7e090d5c59743ecf7e8fd8d05d5130c5bce092db) @@ -4,6 +4,71 @@ ::nx::configure defaultMethodCallProtection false + +nx::test case name-validity-checks { + + nx::Class create C + + # + # Add some basic tests on valid/invalid method names. + # + + ? {set ::h [nsf::method::create ::C "" {} {;}]} "invalid method name ''" + ? {set ::h [nsf::method::create ::C {e1 m1} {} {;}]} "invalid method name 'e1 m1'" + ? {set ::h [nsf::method::create ::C "e1\tm1" {} {;}]} "invalid method name 'e1\tm1'" + ? {set ::h [nsf::method::create ::C {{e1 m1}} {} {;}]} "invalid method name '{e1 m1}'" + ? {set ::h [nsf::method::create ::C ":" {} {;}]} {can't create procedure ":" in non-global namespace with name starting with ":"} + + # These are Tcl whitespace characters, which act as the separators in + # Tcl list string reps: + # + # \u0009 \t TAB + # \u000A \n NEWLINE + # \u000B \v VERTICAL TAB + # \u000C \f FORM FEED + # \u000D \r CARRIAGE RETURN + # \u0020 SPACE + # + + ? {set ::h [nsf::method::create ::C " e1 " {} {;}]} "invalid method name ' e1 '" + ? {set ::h [nsf::method::create ::C {" e1 "} {} {;}]} {invalid method name '" e1 "'} + + ? {set ::h [nsf::method::create ::C "\te1" {} {;}]} "invalid method name '\te1'" + ? {set ::h [nsf::method::create ::C "e1\tm1" {} {;}]} "invalid method name 'e1\tm1'" + + ? {set ::h [nsf::method::create ::C "\ne1" {} {;}]} "invalid method name '\ne1'" + ? {set ::h [nsf::method::create ::C "e1\nm1" {} {;}]} "invalid method name 'e1\nm1'" + + ? {set ::h [nsf::method::create ::C "\ve1" {} {;}]} "invalid method name '\ve1'" + ? {set ::h [nsf::method::create ::C "e1\vm1" {} {;}]} "invalid method name 'e1\vm1'" + + ? {set ::h [nsf::method::create ::C "\fe1" {} {;}]} "invalid method name '\fe1'" + ? {set ::h [nsf::method::create ::C "e1\fm1" {} {;}]} "invalid method name 'e1\fm1'" + + ? {set ::h [nsf::method::create ::C "\re1" {} {;}]} "invalid method name '\re1'" + ? {set ::h [nsf::method::create ::C "e1\rm1" {} {;}]} "invalid method name 'e1\rm1'" + + + # There is no tangible difference between a bareword and a one-element + # list in Tcl (singelton list). So, there will remain exotique method + # names including curly braces, along with other peculiar names, + # e.g. those starting with #. + ? {set ::h [nsf::method::create ::C {{{{{a}}}}} {} {;}]} {::nsf::classes::C::{{{{a}}}}}; + ? {set ::h [nsf::method::create ::C {#a} {} {;}]} {::nsf::classes::C::#a}; + + # + # In Tcl, the empty string is a valid command (proc) name, with + # obscure effects (e.g., cannot be renamed, unless) . We disallow it as method name. + # + + ? {set ::h [nsf::method::create ::C "" {} {;}]} "invalid method name ''" + + # But, we can safeguard against list elements containing Tcl + # whitespace characters at any nesting level. + ? {set ::h [nsf::method::create ::C {{{{{a b}}}}} {} {;}]} {invalid method name '{{{{a b}}}}'}; +} + + nx::test configure -count 10 nx::Class create C {