Run simplifyPlatformTypes on library return types (#1672)
Add a call to simplifyPlatformTypes() in SymbolDatabase::setValueTypeInTokenList() to simplify return types of library configured functions. This fixes the FN in #8141. Regression tests are added, both for the original issue and another FN in the comments. In order to do that, move simplifyPlatformTypes() to TokenList from Tokenizer. This is a pure refactoring and does not change any behaviour. The code was literally copy-pasted from one file to another and in two places 'list.front()' was changed to 'front()'. When adding the call to simplifyPlatformTypes(), the original type of v.size() where v is a container is changed from 'size_t' to 'std::size_t'. Tests are updated accordingly. It can be noted that if v is declared as 'class fred : public std::vector<int> {} v', the original type of 'v.size()' is still 'size_t' and not 'std::size_t'.
This commit is contained in:
parent
fc84227668
commit
dc4e7cef88
|
@ -5417,6 +5417,7 @@ void SymbolDatabase::setValueTypeInTokenList()
|
|||
if (tokenList.createTokens(istr)) {
|
||||
ValueType vt;
|
||||
assert(tokenList.front());
|
||||
tokenList.simplifyPlatformTypes();
|
||||
tokenList.simplifyStdType();
|
||||
if (parsedecl(tokenList.front(), &vt, mDefaultSignedness, mSettings)) {
|
||||
setValueType(tok, vt);
|
||||
|
|
115
lib/tokenize.cpp
115
lib/tokenize.cpp
|
@ -3867,7 +3867,7 @@ bool Tokenizer::simplifyTokenList1(const char FileName[])
|
|||
// convert platform dependent types to standard types
|
||||
// 32 bits: size_t -> unsigned long
|
||||
// 64 bits: size_t -> unsigned long long
|
||||
simplifyPlatformTypes();
|
||||
list.simplifyPlatformTypes();
|
||||
|
||||
// collapse compound standard types into a single token
|
||||
// unsigned long long int => long (with _isUnsigned=true,_isLong=true)
|
||||
|
@ -5972,119 +5972,6 @@ void Tokenizer::simplifyVarDecl(Token * tokBegin, const Token * const tokEnd, co
|
|||
}
|
||||
}
|
||||
|
||||
void Tokenizer::simplifyPlatformTypes()
|
||||
{
|
||||
const bool isCPP11 = mSettings->standards.cpp >= Standards::CPP11;
|
||||
|
||||
enum { isLongLong, isLong, isInt } type;
|
||||
|
||||
/** @todo This assumes a flat address space. Not true for segmented address space (FAR *). */
|
||||
|
||||
if (mSettings->sizeof_size_t == mSettings->sizeof_long)
|
||||
type = isLong;
|
||||
else if (mSettings->sizeof_size_t == mSettings->sizeof_long_long)
|
||||
type = isLongLong;
|
||||
else if (mSettings->sizeof_size_t == mSettings->sizeof_int)
|
||||
type = isInt;
|
||||
else
|
||||
return;
|
||||
|
||||
for (Token *tok = list.front(); tok; tok = tok->next()) {
|
||||
// pre-check to reduce unneeded match calls
|
||||
if (!Token::Match(tok, "std| ::| %type%"))
|
||||
continue;
|
||||
bool isUnsigned;
|
||||
if (Token::Match(tok, "std| ::| size_t|uintptr_t|uintmax_t")) {
|
||||
if (isCPP11 && tok->strAt(-1) == "using" && tok->strAt(1) == "=")
|
||||
continue;
|
||||
isUnsigned = true;
|
||||
} else if (Token::Match(tok, "std| ::| ssize_t|ptrdiff_t|intptr_t|intmax_t")) {
|
||||
if (isCPP11 && tok->strAt(-1) == "using" && tok->strAt(1) == "=")
|
||||
continue;
|
||||
isUnsigned = false;
|
||||
} else
|
||||
continue;
|
||||
|
||||
bool inStd = false;
|
||||
if (tok->str() == "::") {
|
||||
tok->deleteThis();
|
||||
} else if (tok->str() == "std") {
|
||||
if (tok->next()->str() != "::")
|
||||
continue;
|
||||
inStd = true;
|
||||
tok->deleteNext();
|
||||
tok->deleteThis();
|
||||
}
|
||||
|
||||
if (inStd)
|
||||
tok->originalName("std::" + tok->str());
|
||||
else
|
||||
tok->originalName(tok->str());
|
||||
if (isUnsigned)
|
||||
tok->isUnsigned(true);
|
||||
|
||||
switch (type) {
|
||||
case isLongLong:
|
||||
tok->isLong(true);
|
||||
tok->str("long");
|
||||
break;
|
||||
case isLong:
|
||||
tok->str("long");
|
||||
break;
|
||||
case isInt:
|
||||
tok->str("int");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string platform_type(mSettings->platformString());
|
||||
|
||||
for (Token *tok = list.front(); tok; tok = tok->next()) {
|
||||
if (tok->tokType() != Token::eType && tok->tokType() != Token::eName)
|
||||
continue;
|
||||
|
||||
const Library::PlatformType * const platformtype = mSettings->library.platform_type(tok->str(), platform_type);
|
||||
|
||||
if (platformtype) {
|
||||
// check for namespace
|
||||
if (tok->strAt(-1) == "::") {
|
||||
const Token * tok1 = tok->tokAt(-2);
|
||||
// skip when non-global namespace defined
|
||||
if (tok1 && tok1->tokType() == Token::eName)
|
||||
continue;
|
||||
tok = tok->previous();
|
||||
tok->deleteThis();
|
||||
}
|
||||
Token *typeToken;
|
||||
if (platformtype->_const_ptr) {
|
||||
tok->str("const");
|
||||
tok->insertToken("*");
|
||||
tok->insertToken(platformtype->mType);
|
||||
typeToken = tok;
|
||||
} else if (platformtype->_pointer) {
|
||||
tok->str(platformtype->mType);
|
||||
typeToken = tok;
|
||||
tok->insertToken("*");
|
||||
} else if (platformtype->_ptr_ptr) {
|
||||
tok->str(platformtype->mType);
|
||||
typeToken = tok;
|
||||
tok->insertToken("*");
|
||||
tok->insertToken("*");
|
||||
} else {
|
||||
tok->originalName(tok->str());
|
||||
tok->str(platformtype->mType);
|
||||
typeToken = tok;
|
||||
}
|
||||
if (platformtype->_signed)
|
||||
typeToken->isSigned(true);
|
||||
if (platformtype->_unsigned)
|
||||
typeToken->isUnsigned(true);
|
||||
if (platformtype->_long)
|
||||
typeToken->isLong(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Tokenizer::simplifyStaticConst()
|
||||
{
|
||||
// This function will simplify the token list so that the qualifiers "extern", "static"
|
||||
|
|
|
@ -285,13 +285,6 @@ public:
|
|||
void simplifyInitVar();
|
||||
Token * initVar(Token * tok);
|
||||
|
||||
/**
|
||||
* Convert platform dependent types to standard types.
|
||||
* 32 bits: size_t -> unsigned long
|
||||
* 64 bits: size_t -> unsigned long long
|
||||
*/
|
||||
void simplifyPlatformTypes();
|
||||
|
||||
/**
|
||||
* Simplify easy constant '?:' operation
|
||||
* Example: 0 ? (2/0) : 0 => 0
|
||||
|
|
|
@ -1278,6 +1278,119 @@ bool TokenList::validateToken(const Token* tok) const
|
|||
return false;
|
||||
}
|
||||
|
||||
void TokenList::simplifyPlatformTypes()
|
||||
{
|
||||
const bool isCPP11 = mSettings->standards.cpp >= Standards::CPP11;
|
||||
|
||||
enum { isLongLong, isLong, isInt } type;
|
||||
|
||||
/** @todo This assumes a flat address space. Not true for segmented address space (FAR *). */
|
||||
|
||||
if (mSettings->sizeof_size_t == mSettings->sizeof_long)
|
||||
type = isLong;
|
||||
else if (mSettings->sizeof_size_t == mSettings->sizeof_long_long)
|
||||
type = isLongLong;
|
||||
else if (mSettings->sizeof_size_t == mSettings->sizeof_int)
|
||||
type = isInt;
|
||||
else
|
||||
return;
|
||||
|
||||
for (Token *tok = front(); tok; tok = tok->next()) {
|
||||
// pre-check to reduce unneeded match calls
|
||||
if (!Token::Match(tok, "std| ::| %type%"))
|
||||
continue;
|
||||
bool isUnsigned;
|
||||
if (Token::Match(tok, "std| ::| size_t|uintptr_t|uintmax_t")) {
|
||||
if (isCPP11 && tok->strAt(-1) == "using" && tok->strAt(1) == "=")
|
||||
continue;
|
||||
isUnsigned = true;
|
||||
} else if (Token::Match(tok, "std| ::| ssize_t|ptrdiff_t|intptr_t|intmax_t")) {
|
||||
if (isCPP11 && tok->strAt(-1) == "using" && tok->strAt(1) == "=")
|
||||
continue;
|
||||
isUnsigned = false;
|
||||
} else
|
||||
continue;
|
||||
|
||||
bool inStd = false;
|
||||
if (tok->str() == "::") {
|
||||
tok->deleteThis();
|
||||
} else if (tok->str() == "std") {
|
||||
if (tok->next()->str() != "::")
|
||||
continue;
|
||||
inStd = true;
|
||||
tok->deleteNext();
|
||||
tok->deleteThis();
|
||||
}
|
||||
|
||||
if (inStd)
|
||||
tok->originalName("std::" + tok->str());
|
||||
else
|
||||
tok->originalName(tok->str());
|
||||
if (isUnsigned)
|
||||
tok->isUnsigned(true);
|
||||
|
||||
switch (type) {
|
||||
case isLongLong:
|
||||
tok->isLong(true);
|
||||
tok->str("long");
|
||||
break;
|
||||
case isLong:
|
||||
tok->str("long");
|
||||
break;
|
||||
case isInt:
|
||||
tok->str("int");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string platform_type(mSettings->platformString());
|
||||
|
||||
for (Token *tok = front(); tok; tok = tok->next()) {
|
||||
if (tok->tokType() != Token::eType && tok->tokType() != Token::eName)
|
||||
continue;
|
||||
|
||||
const Library::PlatformType * const platformtype = mSettings->library.platform_type(tok->str(), platform_type);
|
||||
|
||||
if (platformtype) {
|
||||
// check for namespace
|
||||
if (tok->strAt(-1) == "::") {
|
||||
const Token * tok1 = tok->tokAt(-2);
|
||||
// skip when non-global namespace defined
|
||||
if (tok1 && tok1->tokType() == Token::eName)
|
||||
continue;
|
||||
tok = tok->previous();
|
||||
tok->deleteThis();
|
||||
}
|
||||
Token *typeToken;
|
||||
if (platformtype->_const_ptr) {
|
||||
tok->str("const");
|
||||
tok->insertToken("*");
|
||||
tok->insertToken(platformtype->mType);
|
||||
typeToken = tok;
|
||||
} else if (platformtype->_pointer) {
|
||||
tok->str(platformtype->mType);
|
||||
typeToken = tok;
|
||||
tok->insertToken("*");
|
||||
} else if (platformtype->_ptr_ptr) {
|
||||
tok->str(platformtype->mType);
|
||||
typeToken = tok;
|
||||
tok->insertToken("*");
|
||||
tok->insertToken("*");
|
||||
} else {
|
||||
tok->originalName(tok->str());
|
||||
tok->str(platformtype->mType);
|
||||
typeToken = tok;
|
||||
}
|
||||
if (platformtype->_signed)
|
||||
typeToken->isSigned(true);
|
||||
if (platformtype->_unsigned)
|
||||
typeToken->isUnsigned(true);
|
||||
if (platformtype->_long)
|
||||
typeToken->isLong(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TokenList::simplifyStdType()
|
||||
{
|
||||
for (Token *tok = front(); tok; tok = tok->next()) {
|
||||
|
|
|
@ -169,6 +169,13 @@ public:
|
|||
*/
|
||||
bool validateToken(const Token* tok) const;
|
||||
|
||||
/**
|
||||
* Convert platform dependent types to standard types.
|
||||
* 32 bits: size_t -> unsigned long
|
||||
* 64 bits: size_t -> unsigned long long
|
||||
*/
|
||||
void simplifyPlatformTypes();
|
||||
|
||||
/**
|
||||
* Collapse compound standard types into a single token.
|
||||
* unsigned long long int => long _isUnsigned=true,_isLong=true
|
||||
|
|
|
@ -2867,10 +2867,10 @@ private:
|
|||
" printf(\"%zu %Iu %d %f\", v.size(), v.size(), v.size(), v.size());\n"
|
||||
" printf(\"%zu %Iu %d %f\", s.size(), s.size(), s.size(), s.size());\n"
|
||||
"}\n", false, true, Settings::Win32A);
|
||||
ASSERT_EQUALS("[test.cpp:4]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:4]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'size_t {aka unsigned long}'.\n", errout.str());
|
||||
ASSERT_EQUALS("[test.cpp:4]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:4]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'std::size_t {aka unsigned long}'.\n", errout.str());
|
||||
|
||||
check("std::vector<int> v;\n"
|
||||
"std::string s;\n"
|
||||
|
@ -2878,10 +2878,10 @@ private:
|
|||
" printf(\"%zu %Iu %d %f\", v.size(), v.size(), v.size(), v.size());\n"
|
||||
" printf(\"%zu %Iu %d %f\", s.size(), s.size(), s.size(), s.size());\n"
|
||||
"}\n", false, true, Settings::Win64);
|
||||
ASSERT_EQUALS("[test.cpp:4]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'size_t {aka unsigned long long}'.\n"
|
||||
"[test.cpp:4]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'size_t {aka unsigned long long}'.\n"
|
||||
"[test.cpp:5]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'size_t {aka unsigned long long}'.\n"
|
||||
"[test.cpp:5]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'size_t {aka unsigned long long}'.\n", errout.str());
|
||||
ASSERT_EQUALS("[test.cpp:4]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'std::size_t {aka unsigned long long}'.\n"
|
||||
"[test.cpp:4]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'std::size_t {aka unsigned long long}'.\n"
|
||||
"[test.cpp:5]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'std::size_t {aka unsigned long long}'.\n"
|
||||
"[test.cpp:5]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'std::size_t {aka unsigned long long}'.\n", errout.str());
|
||||
|
||||
check("std::vector<int> v;\n"
|
||||
"std::string s;\n"
|
||||
|
@ -2889,10 +2889,10 @@ private:
|
|||
" printf(\"%zu %Iu %d %f\", v.size(), v.size(), v.size(), v.size());\n"
|
||||
" printf(\"%zu %Iu %d %f\", s.size(), s.size(), s.size(), s.size());\n"
|
||||
"}\n", false, true, Settings::Unix32);
|
||||
ASSERT_EQUALS("[test.cpp:4]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:4]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'size_t {aka unsigned long}'.\n", errout.str());
|
||||
ASSERT_EQUALS("[test.cpp:4]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:4]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'std::size_t {aka unsigned long}'.\n", errout.str());
|
||||
|
||||
check("std::vector<int> v;\n"
|
||||
"std::string s;\n"
|
||||
|
@ -2900,10 +2900,10 @@ private:
|
|||
" printf(\"%zu %Iu %d %f\", v.size(), v.size(), v.size(), v.size());\n"
|
||||
" printf(\"%zu %Iu %d %f\", s.size(), s.size(), s.size(), s.size());\n"
|
||||
"}\n", false, true, Settings::Unix64);
|
||||
ASSERT_EQUALS("[test.cpp:4]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:4]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'size_t {aka unsigned long}'.\n", errout.str());
|
||||
ASSERT_EQUALS("[test.cpp:4]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:4]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'std::size_t {aka unsigned long}'.\n", errout.str());
|
||||
|
||||
check("class Fred : public std::vector<int> {} v;\n"
|
||||
"std::string s;\n"
|
||||
|
@ -2913,8 +2913,8 @@ private:
|
|||
"}\n", false, true, Settings::Unix64);
|
||||
ASSERT_EQUALS("[test.cpp:4]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:4]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'size_t {aka unsigned long}'.\n", errout.str());
|
||||
"[test.cpp:5]: (portability) %d in format string (no. 3) requires 'int' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:5]: (portability) %f in format string (no. 4) requires 'double' but the argument type is 'std::size_t {aka unsigned long}'.\n", errout.str());
|
||||
|
||||
check("class Fred : public std::vector<int> {} v;\n"
|
||||
"void foo() {\n"
|
||||
|
@ -2946,11 +2946,11 @@ private:
|
|||
ASSERT_EQUALS("[test.cpp:8]: (warning) %u in format string (no. 1) requires 'unsigned int' but the argument type is 'char *'.\n"
|
||||
"[test.cpp:8]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'char *'.\n"
|
||||
"[test.cpp:8]: (warning) %u in format string (no. 3) requires 'unsigned int' but the argument type is 'char *'.\n"
|
||||
"[test.cpp:9]: (portability) %u in format string (no. 1) requires 'unsigned int' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:9]: (portability) %u in format string (no. 1) requires 'unsigned int' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:9]: (portability) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:10]: (portability) %lu in format string (no. 1) requires 'unsigned long' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:10]: (portability) %lu in format string (no. 1) requires 'unsigned long' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:10]: (portability) %lu in format string (no. 2) requires 'unsigned long' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:11]: (portability) %llu in format string (no. 1) requires 'unsigned long long' but the argument type is 'size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:11]: (portability) %llu in format string (no. 1) requires 'unsigned long long' but the argument type is 'std::size_t {aka unsigned long}'.\n"
|
||||
"[test.cpp:11]: (portability) %llu in format string (no. 2) requires 'unsigned long long' but the argument type is 'size_t {aka unsigned long}'.\n", errout.str());
|
||||
|
||||
check("bool b; bool bf();\n"
|
||||
|
@ -4686,6 +4686,12 @@ private:
|
|||
" printf(\"%ld%lld\", atol(s), atoll(s));\n"
|
||||
"}");
|
||||
ASSERT_EQUALS("", errout.str());
|
||||
|
||||
// 8141
|
||||
check("void f(int i) {\n"
|
||||
" printf(\"%f\", imaxabs(i));\n"
|
||||
"}\n", false, true, Settings::Unix64);
|
||||
ASSERT_EQUALS("[test.cpp:2]: (portability) %f in format string (no. 1) requires 'double' but the argument type is 'intmax_t {aka signed long}'.\n", errout.str());
|
||||
}
|
||||
|
||||
void testPrintfTypeAlias1() {
|
||||
|
|
|
@ -55,6 +55,7 @@ private:
|
|||
TEST_CASE(zeroDiv9);
|
||||
TEST_CASE(zeroDiv10);
|
||||
TEST_CASE(zeroDiv11);
|
||||
TEST_CASE(zeroDiv12);
|
||||
|
||||
TEST_CASE(zeroDivCond); // division by zero / useless condition
|
||||
|
||||
|
@ -531,6 +532,14 @@ private:
|
|||
ASSERT_EQUALS("", errout.str());
|
||||
}
|
||||
|
||||
void zeroDiv12() {
|
||||
// #8141
|
||||
check("intmax_t f() {\n"
|
||||
" return 1 / imaxabs(0);\n"
|
||||
"}\n");
|
||||
ASSERT_EQUALS("[test.cpp:2]: (error) Division by zero.\n", errout.str());
|
||||
}
|
||||
|
||||
void zeroDivCond() {
|
||||
check("void f(unsigned int x) {\n"
|
||||
" int y = 17 / x;\n"
|
||||
|
|
Loading…
Reference in New Issue