diff --git a/cfg/posix.cfg b/cfg/posix.cfg new file mode 100644 index 000000000..1302533a2 --- /dev/null +++ b/cfg/posix.cfg @@ -0,0 +1,5 @@ + + + false 0-999999 + + diff --git a/cfg/std.cfg b/cfg/std.cfg index 39fa8feb5..e3bb60e2a 100644 --- a/cfg/std.cfg +++ b/cfg/std.cfg @@ -25,10 +25,11 @@ false false - false - false - false - false + false 0- + false 0- + false 0- + false 0- + false 0- false false @@ -36,9 +37,18 @@ false false false - false - false - false + false 0- + false 0- + false 0- false + + 0,2-36 + 0,2-36 + 0,2-36 + 0,2-36 + 0,2-36 + 0,2-36 + 0,2-36 + 0,2-36 diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index 0187d7026..ccfadfdf7 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -157,6 +157,9 @@ int CppCheckExecutor::check(int argc, const char* const argv[]) return EXIT_FAILURE; } + if (settings.standards.posix) + settings.library.load(argv[0], "posix"); + if (settings.reportProgress) time1 = std::time(0); diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 15e51c003..70b0d6374 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -516,9 +516,6 @@ Settings MainWindow::GetCppcheckSettings() { Settings result; - const QString applicationFilePath = QCoreApplication::applicationFilePath(); - result.library.load(applicationFilePath.toLatin1(), "std"); - // If project file loaded, read settings from it if (mProject) { ProjectFile *pfile = mProject->GetProjectFile(); @@ -567,6 +564,11 @@ Settings MainWindow::GetCppcheckSettings() result.standards.c = mSettings->value(SETTINGS_STD_C99, true).toBool() ? Standards::C99 : (mSettings->value(SETTINGS_STD_C11, false).toBool() ? Standards::C11 : Standards::C89); result.standards.posix = mSettings->value(SETTINGS_STD_POSIX, false).toBool(); + const QString applicationFilePath = QCoreApplication::applicationFilePath(); + result.library.load(applicationFilePath.toLatin1(), "std"); + if (result.standards.posix) + result.library.load(applicationFilePath.toLatin1(), "posix"); + if (result._jobs <= 1) { result._jobs = 1; } diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 33f93e591..99fff7e31 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -579,40 +579,6 @@ void CheckOther::checkPipeParameterSizeError(const Token *tok, const std::string "The variable '" + strVarName + "' is an array of size " + strDim + ", which does not match."); } -//----------------------------------------------------------------------------- -// check usleep(), which is allowed to be called with in a range of [0,999999] -// -// Reference: -// - http://man7.org/linux/man-pages/man3/usleep.3.html -//----------------------------------------------------------------------------- -void CheckOther::checkSleepTimeInterval() -{ - if (!_settings->standards.posix) - return; - - const SymbolDatabase *symbolDatabase = _tokenizer->getSymbolDatabase(); - const std::size_t functions = symbolDatabase->functionScopes.size(); - for (std::size_t i = 0; i < functions; ++i) { - const Scope * scope = symbolDatabase->functionScopes[i]; - for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) { - if (Token::Match(tok, "usleep ( %num% )")) { - const Token * const numTok = tok->tokAt(2); - MathLib::bigint value = MathLib::toLongNumber(numTok->str()); - if (value > 999999) { // less than 1 million - checkSleepTimeError(numTok, numTok->str()); - } - } - } - } -} - -void CheckOther::checkSleepTimeError(const Token *tok, const std::string &strDim) -{ - reportError(tok, Severity::error, - "tooBigSleepTime", "The argument of usleep must be less than 1000000.\n" - "The argument of usleep must be less than 1000000, but " + strDim + " is provided."); -} - //--------------------------------------------------------------------------- // Detect redundant assignments: x = 0; x = 4; //--------------------------------------------------------------------------- @@ -1472,24 +1438,40 @@ void CheckOther::redundantConditionError(const Token *tok, const std::string &te //--------------------------------------------------------------------------- void CheckOther::invalidFunctionUsage() { - // strtol and strtoul.. - for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) { - if (!Token::Match(tok, "strtol|strtoul|strtoll|strtoull|wcstol|wcstoul|wcstoll|wcstoull (")) - continue; + const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase(); + const std::size_t functions = symbolDatabase->functionScopes.size(); + for (std::size_t i = 0; i < functions; ++i) { + const Scope * scope = symbolDatabase->functionScopes[i]; + for (const Token* tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) { + if (!Token::Match(tok, "%var% ( !!)")) + continue; + const std::string functionName = tok->str(); + int argnr = 1; + const Token *argtok = tok->tokAt(2); + while (argtok && argtok->str() != ")") { + if (Token::Match(argtok,"%num% [,)]")) { + if (MathLib::isInt(argtok->str()) && + !_settings->library.isargvalid(functionName, argnr, MathLib::toLongNumber(argtok->str()))) + invalidFunctionArgError(argtok,functionName,argnr,_settings->library.validarg(functionName,argnr)); + } else { + const Token *top = argtok; + while (top->astParent() && top->astParent()->str() != "," && top->astParent() != tok->next()) + top = top->astParent(); + if (top->isComparisonOp() || Token::Match(top, "%oror%|&&")) { + if (_settings->library.isboolargbad(functionName, argnr)) + invalidFunctionArgBoolError(top, functionName, argnr); - const std::string& funcname = tok->str(); - tok = tok->tokAt(2); - // Locate the third parameter of the function call.. - for (int i = 0; i < 2 && tok; i++) - tok = tok->nextArgument(); - - if (Token::Match(tok, "%num% )")) { - const MathLib::bigint radix = MathLib::toLongNumber(tok->str()); - if (!(radix == 0 || (radix >= 2 && radix <= 36))) { - dangerousUsageStrtolError(tok, funcname); + // Are the values 0 and 1 valid? + else if (!_settings->library.isargvalid(functionName, argnr, 0)) + invalidFunctionArgError(top, functionName, argnr, _settings->library.validarg(functionName,argnr)); + else if (!_settings->library.isargvalid(functionName, argnr, 1)) + invalidFunctionArgError(top, functionName, argnr, _settings->library.validarg(functionName,argnr)); + } + } + argnr++; + argtok = argtok->nextArgument(); } - } else - break; + } } // sprintf|snprintf overlapping data @@ -1529,11 +1511,6 @@ void CheckOther::invalidFunctionUsage() } } -void CheckOther::dangerousUsageStrtolError(const Token *tok, const std::string& funcname) -{ - reportError(tok, Severity::error, "dangerousUsageStrtol", "Invalid radix in call to " + funcname + "(). It must be 0 or 2-36."); -} - void CheckOther::sprintfOverlappingDataError(const Token *tok, const std::string &varname) { reportError(tok, Severity::error, "sprintfOverlappingData", @@ -1545,6 +1522,26 @@ void CheckOther::sprintfOverlappingDataError(const Token *tok, const std::string "to sprintf() or snprintf(), the results are undefined.\""); } +void CheckOther::invalidFunctionArgError(const Token *tok, const std::string &functionName, int argnr, const std::string &validstr) +{ + std::ostringstream errmsg; + errmsg << "Invalid " << functionName << "() argument nr " << argnr; + if (!tok) + ; + else if (tok->isNumber()) + errmsg << ". The value is " << tok->str() << " but the valid values are '" << validstr << "'."; + else if (tok->isComparisonOp()) + errmsg << ". The value is 0 or 1 (comparison result) but the valid values are '" << validstr << "'."; + reportError(tok, Severity::error, "invalidFunctionArg", errmsg.str()); +} + +void CheckOther::invalidFunctionArgBoolError(const Token *tok, const std::string &functionName, int argnr) +{ + std::ostringstream errmsg; + errmsg << "Invalid " << functionName << "() argument nr " << argnr << ". A non-boolean value is required."; + reportError(tok, Severity::error, "invalidFunctionArgBool", errmsg.str()); +} + //--------------------------------------------------------------------------- // Find consecutive return, break, continue, goto or throw statements. e.g.: // break; break; diff --git a/lib/checkother.h b/lib/checkother.h index c4cab33c2..a8a95b945 100644 --- a/lib/checkother.h +++ b/lib/checkother.h @@ -109,7 +109,6 @@ public: checkOther.checkRedundantCopy(); checkOther.checkNegativeBitwiseShift(); checkOther.checkSuspiciousEqualityComparison(); - checkOther.checkSleepTimeInterval(); checkOther.checkComparisonFunctionIsAlwaysTrueOrFalse(); } @@ -132,11 +131,12 @@ public: void invalidPointerCast(); /** - * @brief Invalid function usage (invalid radix / overlapping data) + * @brief Invalid function usage (invalid input value / overlapping data) * * %Check that given function parameters are valid according to the standard * - wrong radix given for strtol/strtoul * - overlapping data when using sprintf/snprintf + * - wrong input value according to library */ void invalidFunctionUsage(); @@ -263,9 +263,6 @@ public: /** @brief %Check to avoid casting a return value to unsigned char and then back to integer type. */ void checkCastIntToCharAndBack(); - /** @brief %Check providing too big sleep time intervals on POSIX systems. */ - void checkSleepTimeInterval(); - /** @brief %Check for using of comparison functions evaluating always to true or false. */ void checkComparisonFunctionIsAlwaysTrueOrFalse(void); @@ -275,7 +272,6 @@ private: // Error messages.. void checkComparisonFunctionIsAlwaysTrueOrFalseError(const Token* tok, const std::string &strFunctionName, const std::string &varName, const bool result); - void checkSleepTimeError(const Token *tok, const std::string &strDim); void checkCastIntToCharAndBackError(const Token *tok, const std::string &strFunctionName); void checkPipeParameterSizeError(const Token *tok, const std::string &strVarName, const std::string &strDim); void oppositeInnerConditionError(const Token *tok); @@ -285,8 +281,9 @@ private: void redundantGetAndSetUserIdError(const Token *tok); void cstyleCastError(const Token *tok); void invalidPointerCastError(const Token* tok, const std::string& from, const std::string& to, bool inconclusive); - void dangerousUsageStrtolError(const Token *tok, const std::string& funcname); void sprintfOverlappingDataError(const Token *tok, const std::string &varname); + void invalidFunctionArgError(const Token *tok, const std::string &functionName, int argnr, const std::string &validstr); + void invalidFunctionArgBoolError(const Token *tok, const std::string &functionName, int argnr); void udivError(const Token *tok, bool inconclusive); void passedByValueError(const Token *tok, const std::string &parname); void constStatementError(const Token *tok, const std::string &type); @@ -340,6 +337,8 @@ private: // error c.sprintfOverlappingDataError(0, "varname"); + c.invalidFunctionArgError(0, "func_name", 1, "1-4"); + c.invalidFunctionArgBoolError(0, "func_name", 1); c.udivError(0, false); c.zerodivError(0); c.zerodivcondError(0,0); @@ -349,7 +348,6 @@ private: c.invalidPointerCastError(0, "float", "double", false); c.negativeBitwiseShiftError(0); c.checkPipeParameterSizeError(0, "varname", "dimension"); - c.checkSleepTimeError(0,"dimension"); //performance c.redundantCopyError(0, "varname"); @@ -361,7 +359,6 @@ private: c.checkCastIntToCharAndBackError(0,"func_name"); c.oppositeInnerConditionError(0); c.cstyleCastError(0); - c.dangerousUsageStrtolError(0, "strtol"); c.passedByValueError(0, "parametername"); c.constStatementError(0, "type"); c.charArrayIndexError(0); @@ -421,7 +418,7 @@ private: "* bitwise operation with negative right operand\n" "* provide wrong dimensioned array to pipe() system command (--std=posix)\n" "* cast the return values of getc(),fgetc() and getchar() to character and compare it to EOF\n" - "* provide too big sleep times on POSIX systems\n" + "* invalid input values for functions\n" // warning "* either division by zero or useless condition\n" @@ -435,7 +432,6 @@ private: "* C-style pointer cast in cpp file\n" "* casting between incompatible pointer types\n" "* redundant if\n" - "* bad usage of the function 'strtol'\n" "* [[CheckUnsignedDivision|unsigned division]]\n" "* passing parameter by value\n" "* [[IncompleteStatement|Incomplete statement]]\n" diff --git a/lib/library.cpp b/lib/library.cpp index 78a3835cc..c810f4069 100644 --- a/lib/library.cpp +++ b/lib/library.cpp @@ -19,6 +19,9 @@ #include "library.h" #include "path.h" #include "tinyxml2.h" +#include "tokenlist.h" +#include "mathlib.h" +#include "token.h" #include #include @@ -108,13 +111,16 @@ bool Library::load(const tinyxml2::XMLDocument &doc) leakignore.insert(name); else if (strcmp(functionnode->Name(), "arg") == 0 && functionnode->Attribute("nr") != NULL) { const int nr = atoi(functionnode->Attribute("nr")); + bool notbool = false; bool notnull = false; bool notuninit = false; bool formatstr = false; bool strz = false; std::string valid; for (const tinyxml2::XMLElement *argnode = functionnode->FirstChildElement(); argnode; argnode = argnode->NextSiblingElement()) { - if (strcmp(argnode->Name(), "not-null") == 0) + if (strcmp(argnode->Name(), "not-bool") == 0) + notbool = true; + else if (strcmp(argnode->Name(), "not-null") == 0) notnull = true; else if (strcmp(argnode->Name(), "not-uninit") == 0) notuninit = true; @@ -144,6 +150,7 @@ bool Library::load(const tinyxml2::XMLDocument &doc) else return false; } + argumentChecks[name][nr].notbool = notbool; argumentChecks[name][nr].notnull = notnull; argumentChecks[name][nr].notuninit = notuninit; argumentChecks[name][nr].formatstr = formatstr; @@ -249,3 +256,24 @@ bool Library::load(const tinyxml2::XMLDocument &doc) } return true; } + +bool Library::isargvalid(const std::string &functionName, int argnr, const MathLib::bigint argvalue) const +{ + const ArgumentChecks *ac = getarg(functionName, argnr); + if (!ac || ac->valid.empty()) + return true; + TokenList tokenList(0); + std::istringstream istr(ac->valid + ','); + tokenList.createTokens(istr,""); + for (const Token *tok = tokenList.front(); tok; tok = tok->next()) { + if (tok->isNumber() && argvalue == MathLib::toLongNumber(tok->str())) + return true; + if (Token::Match(tok, "%num% - %num%") && argvalue >= MathLib::toLongNumber(tok->str()) && argvalue <= MathLib::toLongNumber(tok->strAt(2))) + return true; + if (Token::Match(tok, "%num% - ,") && argvalue >= MathLib::toLongNumber(tok->str())) + return true; + if ((!tok->previous() || tok->previous()->str() == ",") && Token::Match(tok,"- %num%") && argvalue <= MathLib::toLongNumber(tok->strAt(1))) + return true; + } + return false; +} diff --git a/lib/library.h b/lib/library.h index b325c6335..36ce11978 100644 --- a/lib/library.h +++ b/lib/library.h @@ -23,6 +23,7 @@ #include "config.h" #include "path.h" +#include "mathlib.h" #include #include @@ -31,6 +32,8 @@ #include #include +class TokenList; + /// @addtogroup Core /// @{ @@ -91,11 +94,13 @@ public: return (it != _noreturn.end() && !it->second); } - struct ArgumentChecks { + class ArgumentChecks { + public: ArgumentChecks() { - notnull = notuninit = formatstr = strz = false; + notbool = notnull = notuninit = formatstr = strz = false; } + bool notbool; bool notnull; bool notuninit; bool formatstr; @@ -106,6 +111,11 @@ public: // function name, argument nr => argument data std::map > argumentChecks; + bool isboolargbad(const std::string &functionName, int argnr) const { + const ArgumentChecks *arg = getarg(functionName, argnr); + return arg && arg->notbool; + } + bool isnullargbad(const std::string &functionName, int argnr) const { const ArgumentChecks *arg = getarg(functionName, argnr); return arg && arg->notnull; @@ -126,6 +136,13 @@ public: return arg && arg->strz; } + bool isargvalid(const std::string &functionName, int argnr, const MathLib::bigint argvalue) const; + + std::string validarg(const std::string &functionName, int argnr) const { + const ArgumentChecks *arg = getarg(functionName, argnr); + return arg ? arg->valid : std::string(""); + } + bool markupFile(const std::string &path) const { return _markupExtensions.find(Path::getFilenameExtensionInLowerCase(path)) != _markupExtensions.end(); } diff --git a/test/testlibrary.cpp b/test/testlibrary.cpp index 2b27a8690..d1f98a836 100644 --- a/test/testlibrary.cpp +++ b/test/testlibrary.cpp @@ -17,6 +17,8 @@ */ #include "library.h" +#include "token.h" +#include "tokenlist.h" #include "testsuite.h" class TestLibrary : public TestFixture { @@ -67,21 +69,12 @@ private: const char xmldata[] = "\n" "\n" " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " \n" - " 1-\n" - " \n" + " \n" + " \n" + " \n" + " \n" + " 1-\n" + " \n" " \n" ""; tinyxml2::XMLDocument doc; @@ -94,6 +87,7 @@ private: ASSERT_EQUALS(true, library.argumentChecks["foo"][3].formatstr); ASSERT_EQUALS(true, library.argumentChecks["foo"][4].strz); ASSERT_EQUALS("1-", library.argumentChecks["foo"][5].valid); + ASSERT_EQUALS(true, library.argumentChecks["foo"][6].notbool); } void memory() { diff --git a/test/testother.cpp b/test/testother.cpp index 73a2456ff..b9efd260e 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -49,6 +49,8 @@ private: TEST_CASE(nanInArithmeticExpression); + TEST_CASE(invalidFunctionUsage1); + TEST_CASE(sprintf1); // Dangerous usage of sprintf TEST_CASE(sprintf2); TEST_CASE(sprintf3); @@ -82,8 +84,6 @@ private: TEST_CASE(oldStylePointerCast); TEST_CASE(invalidPointerCast); - TEST_CASE(dangerousStrolUsage); - TEST_CASE(passedByValue); TEST_CASE(mathfunctionCall_fmod); @@ -211,6 +211,16 @@ private: settings->experimental = experimental; settings->standards.posix = posix; + if (posix) { + const char cfg[] = "\n" + "\n" + " 0-999999 \n" + ""; + tinyxml2::XMLDocument xmldoc; + xmldoc.Parse(cfg, sizeof(cfg)); + settings->library.load(xmldoc); + } + // Tokenize.. Tokenizer tokenizer(settings, this); std::istringstream istr(code); @@ -651,64 +661,88 @@ private: } - void sprintfUsage(const char code[]) { + void invalidFunctionUsage(const char code[]) { // Clear the error buffer.. errout.str(""); + const char cfg[] = "\n" + "\n" + " \n" + " 0,2-36 \n" + ""; + tinyxml2::XMLDocument xmldoc; + xmldoc.Parse(cfg, sizeof(cfg)); + Settings settings; + settings.library.load(xmldoc); // Tokenize.. Tokenizer tokenizer(&settings, this); std::istringstream istr(code); tokenizer.tokenize(istr, "test.cpp"); - //tokenizer.tokens()->printOut( "tokens" ); - // Check for redundant code.. CheckOther checkOther(&tokenizer, &settings, this); checkOther.invalidFunctionUsage(); } + void invalidFunctionUsage1() { + invalidFunctionUsage("int f() { memset(a,b,sizeof(a)!=12); }"); + ASSERT_EQUALS("[test.cpp:1]: (error) Invalid memset() argument nr 3. A non-boolean value is required.\n", errout.str()); + + invalidFunctionUsage("int f() { memset(a,b,sizeof(a)!=0); }"); + TODO_ASSERT_EQUALS("error", "", errout.str()); + + invalidFunctionUsage("int f() { strtol(a,b,sizeof(a)!=12); }"); + ASSERT_EQUALS("[test.cpp:1]: (error) Invalid strtol() argument nr 3. The value is 0 or 1 (comparison result) but the valid values are '0,2-36'.\n", errout.str()); + + invalidFunctionUsage("int f() { strtol(a,b,1); }"); + ASSERT_EQUALS("[test.cpp:1]: (error) Invalid strtol() argument nr 3. The value is 1 but the valid values are '0,2-36'.\n", errout.str()); + + invalidFunctionUsage("int f() { strtol(a,b,10); }"); + ASSERT_EQUALS("", errout.str()); + } + void sprintf1() { - sprintfUsage("void foo()\n" - "{\n" - " char buf[100];\n" - " sprintf(buf,\"%s\",buf);\n" - "}"); + invalidFunctionUsage("void foo()\n" + "{\n" + " char buf[100];\n" + " sprintf(buf,\"%s\",buf);\n" + "}"); ASSERT_EQUALS("[test.cpp:4]: (error) Undefined behavior: Variable 'buf' is used as parameter and destination in s[n]printf().\n", errout.str()); } void sprintf2() { - sprintfUsage("void foo()\n" - "{\n" - " char buf[100];\n" - " sprintf(buf,\"%i\",sizeof(buf));\n" - "}"); + invalidFunctionUsage("void foo()\n" + "{\n" + " char buf[100];\n" + " sprintf(buf,\"%i\",sizeof(buf));\n" + "}"); ASSERT_EQUALS("", errout.str()); } void sprintf3() { - sprintfUsage("void foo()\n" - "{\n" - " char buf[100];\n" - " sprintf(buf,\"%i\",sizeof(buf));\n" - " if (buf[0]);\n" - "}"); + invalidFunctionUsage("void foo()\n" + "{\n" + " char buf[100];\n" + " sprintf(buf,\"%i\",sizeof(buf));\n" + " if (buf[0]);\n" + "}"); ASSERT_EQUALS("", errout.str()); } void sprintf4() { - sprintfUsage("struct A\n" - "{\n" - " char filename[128];\n" - "};\n" - "\n" - "void foo()\n" - "{\n" - " const char* filename = \"hello\";\n" - " struct A a;\n" - " snprintf(a.filename, 128, \"%s\", filename);\n" - "}"); + invalidFunctionUsage("struct A\n" + "{\n" + " char filename[128];\n" + "};\n" + "\n" + "void foo()\n" + "{\n" + " const char* filename = \"hello\";\n" + " struct A a;\n" + " snprintf(a.filename, 128, \"%s\", filename);\n" + "}"); ASSERT_EQUALS("", errout.str()); } @@ -1374,26 +1408,6 @@ private: checkInvalidPointerCast("Q_DECLARE_METATYPE(int*)"); // #4135 - don't crash } - void dangerousStrolUsage() { - { - sprintfUsage("int f(const char *num)\n" - "{\n" - " return strtol(num, NULL, 1);\n" - "}"); - - ASSERT_EQUALS("[test.cpp:3]: (error) Invalid radix in call to strtol(). It must be 0 or 2-36.\n", errout.str()); - } - - { - sprintfUsage("int f(const char *num)\n" - "{\n" - " return strtol(num, NULL, 10);\n" - "}"); - - ASSERT_EQUALS("", errout.str()); - } - } - void testPassedByValue(const char code[]) { // Clear the error buffer.. errout.str(""); @@ -6893,12 +6907,12 @@ private: check("void f(){\n" "usleep(1000000);\n" "}",NULL,false,false,true); - ASSERT_EQUALS("[test.cpp:2]: (error) The argument of usleep must be less than 1000000.\n", errout.str()); + ASSERT_EQUALS("[test.cpp:2]: (error) Invalid usleep() argument nr 1. The value is 1000000 but the valid values are '0-999999'.\n", errout.str()); check("void f(){\n" "usleep(1000001);\n" "}",NULL,false,false,true); - ASSERT_EQUALS("[test.cpp:2]: (error) The argument of usleep must be less than 1000000.\n", errout.str()); + ASSERT_EQUALS("[test.cpp:2]: (error) Invalid usleep() argument nr 1. The value is 1000001 but the valid values are '0-999999'.\n", errout.str()); } void checkCommaSeparatedReturn() {