Skip to content

Commit d95ae44

Browse files
authored
Fixes #13956: VisualStudio Importer: Define UNICODE=1;_UNICODE=1 if CharacterSet is Unicode (#7586)
These defines are set by msbuild if CharacterSet is set to Unicode. [TestCppCheckProject.zip](https://github.com/user-attachments/files/20694856/TestCppCheckProject.zip) This test project compiles fine but the current cppcheck version reports: ``` $ cppcheck --project=TestCppCheckProject.sln --project-configuration="Debug|Win32" Checking main.cpp Debug|Win32... main.cpp:7:0: error: #error "nicht definiert" [preprocessorErrorDirective] #error "nicht definiert" ^ ``` with this PR ``` $ cppcheck --project=TestCppCheckProject.sln --project-configuration="Debug|Win32"... Checking main.cpp Debug|Win32... Checking main.cpp: _WIN32=1;WIN32=1;_DEBUG=1;_CONSOLE=1;__SSE2__=1;UNICODE=1;_UNICODE=1;_MSC_VER=1900... ``` <details><summary>main.cpp</summary> <p> ```cpp #include <iostream> #ifndef _UNICODE #error "nicht definiert" #endif #ifndef UNICODE #error "Not supported" #endif // UNICODE int main() { std::cout << "Hello World!\n"; } ``` </p> </details>
1 parent fc0cc31 commit d95ae44

File tree

4 files changed

+165
-69
lines changed

4 files changed

+165
-69
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ test/testfunctions.o: test/testfunctions.cpp lib/addoninfo.h lib/check.h lib/che
776776
test/testgarbage.o: test/testgarbage.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h
777777
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testgarbage.cpp
778778

779-
test/testimportproject.o: test/testimportproject.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h test/fixture.h test/redirect.h
779+
test/testimportproject.o: test/testimportproject.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h lib/xml.h test/fixture.h test/redirect.h
780780
$(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testimportproject.cpp
781781

782782
test/testincompletestatement.o: test/testincompletestatement.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkother.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h

lib/importproject.cpp

Lines changed: 105 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <stack>
3737
#include <unordered_set>
3838
#include <utility>
39+
#include <vector>
3940

4041
#include "xml.h"
4142

@@ -527,51 +528,11 @@ namespace {
527528
std::string platformStr;
528529
};
529530

530-
struct ItemDefinitionGroup {
531-
explicit ItemDefinitionGroup(const tinyxml2::XMLElement *idg, std::string includePaths) : additionalIncludePaths(std::move(includePaths)) {
531+
struct ConditionalGroup {
532+
explicit ConditionalGroup(const tinyxml2::XMLElement *idg){
532533
const char *condAttr = idg->Attribute("Condition");
533534
if (condAttr)
534-
condition = condAttr;
535-
for (const tinyxml2::XMLElement *e1 = idg->FirstChildElement(); e1; e1 = e1->NextSiblingElement()) {
536-
const char* name = e1->Name();
537-
if (std::strcmp(name, "ClCompile") == 0) {
538-
enhancedInstructionSet = "StreamingSIMDExtensions2";
539-
for (const tinyxml2::XMLElement *e = e1->FirstChildElement(); e; e = e->NextSiblingElement()) {
540-
const char * const text = e->GetText();
541-
if (!text)
542-
continue;
543-
const char * const ename = e->Name();
544-
if (std::strcmp(ename, "PreprocessorDefinitions") == 0)
545-
preprocessorDefinitions = text;
546-
else if (std::strcmp(ename, "AdditionalIncludeDirectories") == 0) {
547-
if (!additionalIncludePaths.empty())
548-
additionalIncludePaths += ';';
549-
additionalIncludePaths += text;
550-
} else if (std::strcmp(ename, "LanguageStandard") == 0) {
551-
if (std::strcmp(text, "stdcpp14") == 0)
552-
cppstd = Standards::CPP14;
553-
else if (std::strcmp(text, "stdcpp17") == 0)
554-
cppstd = Standards::CPP17;
555-
else if (std::strcmp(text, "stdcpp20") == 0)
556-
cppstd = Standards::CPP20;
557-
else if (std::strcmp(text, "stdcpplatest") == 0)
558-
cppstd = Standards::CPPLatest;
559-
} else if (std::strcmp(ename, "EnableEnhancedInstructionSet") == 0) {
560-
enhancedInstructionSet = text;
561-
}
562-
}
563-
}
564-
else if (std::strcmp(name, "Link") == 0) {
565-
for (const tinyxml2::XMLElement *e = e1->FirstChildElement(); e; e = e->NextSiblingElement()) {
566-
const char * const text = e->GetText();
567-
if (!text)
568-
continue;
569-
if (std::strcmp(e->Name(), "EntryPointSymbol") == 0) {
570-
entryPointSymbol = text;
571-
}
572-
}
573-
}
574-
}
535+
mCondition = condAttr;
575536
}
576537

577538
static void replaceAll(std::string &c, const std::string &from, const std::string &to) {
@@ -585,9 +546,9 @@ namespace {
585546
// see https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions
586547
// properties are .NET String objects and you can call any of its members on them
587548
bool conditionIsTrue(const ProjectConfiguration &p) const {
588-
if (condition.empty())
549+
if (mCondition.empty())
589550
return true;
590-
std::string c = '(' + condition + ");";
551+
std::string c = '(' + mCondition + ");";
591552
replaceAll(c, "$(Configuration)", p.configuration);
592553
replaceAll(c, "$(Platform)", p.platformStr);
593554

@@ -623,13 +584,75 @@ namespace {
623584
}
624585
return false;
625586
}
626-
std::string condition;
587+
private:
588+
std::string mCondition;
589+
};
590+
591+
struct ItemDefinitionGroup : ConditionalGroup {
592+
explicit ItemDefinitionGroup(const tinyxml2::XMLElement *idg, std::string includePaths) : ConditionalGroup(idg), additionalIncludePaths(std::move(includePaths)) {
593+
for (const tinyxml2::XMLElement *e1 = idg->FirstChildElement(); e1; e1 = e1->NextSiblingElement()) {
594+
const char* name = e1->Name();
595+
if (std::strcmp(name, "ClCompile") == 0) {
596+
enhancedInstructionSet = "StreamingSIMDExtensions2";
597+
for (const tinyxml2::XMLElement *e = e1->FirstChildElement(); e; e = e->NextSiblingElement()) {
598+
const char * const text = e->GetText();
599+
if (!text)
600+
continue;
601+
const char * const ename = e->Name();
602+
if (std::strcmp(ename, "PreprocessorDefinitions") == 0)
603+
preprocessorDefinitions = text;
604+
else if (std::strcmp(ename, "AdditionalIncludeDirectories") == 0) {
605+
if (!additionalIncludePaths.empty())
606+
additionalIncludePaths += ';';
607+
additionalIncludePaths += text;
608+
} else if (std::strcmp(ename, "LanguageStandard") == 0) {
609+
if (std::strcmp(text, "stdcpp14") == 0)
610+
cppstd = Standards::CPP14;
611+
else if (std::strcmp(text, "stdcpp17") == 0)
612+
cppstd = Standards::CPP17;
613+
else if (std::strcmp(text, "stdcpp20") == 0)
614+
cppstd = Standards::CPP20;
615+
else if (std::strcmp(text, "stdcpplatest") == 0)
616+
cppstd = Standards::CPPLatest;
617+
} else if (std::strcmp(ename, "EnableEnhancedInstructionSet") == 0) {
618+
enhancedInstructionSet = text;
619+
}
620+
}
621+
}
622+
else if (std::strcmp(name, "Link") == 0) {
623+
for (const tinyxml2::XMLElement *e = e1->FirstChildElement(); e; e = e->NextSiblingElement()) {
624+
const char * const text = e->GetText();
625+
if (!text)
626+
continue;
627+
if (std::strcmp(e->Name(), "EntryPointSymbol") == 0) {
628+
entryPointSymbol = text;
629+
}
630+
}
631+
}
632+
}
633+
}
634+
627635
std::string enhancedInstructionSet;
628636
std::string preprocessorDefinitions;
629637
std::string additionalIncludePaths;
630638
std::string entryPointSymbol; // TODO: use this
631639
Standards::cppstd_t cppstd = Standards::CPPLatest;
632640
};
641+
642+
struct ConfigurationPropertyGroup : ConditionalGroup {
643+
explicit ConfigurationPropertyGroup(const tinyxml2::XMLElement *idg) : ConditionalGroup(idg) {
644+
for (const tinyxml2::XMLElement *e = idg->FirstChildElement(); e; e = e->NextSiblingElement()) {
645+
if (std::strcmp(e->Name(), "UseOfMfc") == 0) {
646+
useOfMfc = true;
647+
} else if (std::strcmp(e->Name(), "CharacterSet") == 0) {
648+
useUnicode = std::strcmp(e->GetText(), "Unicode") == 0;
649+
}
650+
}
651+
}
652+
653+
bool useOfMfc = false;
654+
bool useUnicode = false;
655+
};
633656
}
634657

635658
static std::list<std::string> toStringList(const std::string &s)
@@ -648,17 +671,8 @@ static std::list<std::string> toStringList(const std::string &s)
648671
return ret;
649672
}
650673

651-
static void importPropertyGroup(const tinyxml2::XMLElement *node, std::map<std::string,std::string,cppcheck::stricmp> &variables, std::string &includePath, bool *useOfMfc)
674+
static void importPropertyGroup(const tinyxml2::XMLElement *node, std::map<std::string, std::string, cppcheck::stricmp> &variables, std::string &includePath)
652675
{
653-
if (useOfMfc) {
654-
for (const tinyxml2::XMLElement *e = node->FirstChildElement(); e; e = e->NextSiblingElement()) {
655-
if (std::strcmp(e->Name(), "UseOfMfc") == 0) {
656-
*useOfMfc = true;
657-
break;
658-
}
659-
}
660-
}
661-
662676
const char* labelAttribute = node->Attribute("Label");
663677
if (labelAttribute && std::strcmp(labelAttribute, "UserMacros") == 0) {
664678
for (const tinyxml2::XMLElement *propertyGroup = node->FirstChildElement(); propertyGroup; propertyGroup = propertyGroup->NextSiblingElement()) {
@@ -719,31 +733,39 @@ static void loadVisualStudioProperties(const std::string &props, std::map<std::s
719733
}
720734
}
721735
} else if (std::strcmp(name,"PropertyGroup")==0) {
722-
importPropertyGroup(node, variables, includePath, nullptr);
736+
importPropertyGroup(node, variables, includePath);
723737
} else if (std::strcmp(name,"ItemDefinitionGroup")==0) {
724738
itemDefinitionGroupList.emplace_back(node, additionalIncludeDirectories);
725739
}
726740
}
727741
}
728742

729-
bool ImportProject::importVcxproj(const std::string &filename, std::map<std::string, std::string, cppcheck::stricmp> &variables, const std::string &additionalIncludeDirectories, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache)
743+
bool ImportProject::importVcxproj(const std::string &filename,
744+
std::map<std::string, std::string, cppcheck::stricmp> &variables,
745+
const std::string &additionalIncludeDirectories,
746+
const std::vector<std::string> &fileFilters,
747+
std::vector<SharedItemsProject> &cache)
748+
{
749+
tinyxml2::XMLDocument doc;
750+
const tinyxml2::XMLError error = doc.LoadFile(filename.c_str());
751+
if (error != tinyxml2::XML_SUCCESS) {
752+
printError(std::string("Visual Studio project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error));
753+
return false;
754+
}
755+
return importVcxproj(filename, doc, variables, additionalIncludeDirectories, fileFilters, cache);
756+
}
757+
758+
bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::XMLDocument &doc, std::map<std::string, std::string, cppcheck::stricmp> &variables, const std::string &additionalIncludeDirectories, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache)
730759
{
731760
variables["ProjectDir"] = Path::simplifyPath(Path::getPathFromFilename(filename));
732761

733762
std::list<ProjectConfiguration> projectConfigurationList;
734763
std::list<std::string> compileList;
735764
std::list<ItemDefinitionGroup> itemDefinitionGroupList;
765+
std::vector<ConfigurationPropertyGroup> configurationPropertyGroups;
736766
std::string includePath;
737767
std::vector<SharedItemsProject> sharedItemsProjects;
738768

739-
bool useOfMfc = false;
740-
741-
tinyxml2::XMLDocument doc;
742-
const tinyxml2::XMLError error = doc.LoadFile(filename.c_str());
743-
if (error != tinyxml2::XML_SUCCESS) {
744-
printError(std::string("Visual Studio project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error));
745-
return false;
746-
}
747769
const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement();
748770
if (rootnode == nullptr) {
749771
printError("Visual Studio project file has no XML root node");
@@ -777,7 +799,12 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map<std::str
777799
} else if (std::strcmp(name, "ItemDefinitionGroup") == 0) {
778800
itemDefinitionGroupList.emplace_back(node, additionalIncludeDirectories);
779801
} else if (std::strcmp(name, "PropertyGroup") == 0) {
780-
importPropertyGroup(node, variables, includePath, &useOfMfc);
802+
const char* labelAttribute = node->Attribute("Label");
803+
if (labelAttribute && std::strcmp(labelAttribute, "Configuration") == 0) {
804+
configurationPropertyGroups.emplace_back(node);
805+
} else {
806+
importPropertyGroup(node, variables, includePath);
807+
}
781808
} else if (std::strcmp(name, "ImportGroup") == 0) {
782809
const char *labelAttribute = node->Attribute("Label");
783810
if (labelAttribute && std::strcmp(labelAttribute, "PropertySheets") == 0) {
@@ -853,7 +880,6 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map<std::str
853880
fs.cfg = p.name;
854881
// TODO: detect actual MSC version
855882
fs.msc = true;
856-
fs.useMfc = useOfMfc;
857883
fs.defines = "_WIN32=1";
858884
if (p.platform == ProjectConfiguration::Win32)
859885
fs.platformType = Platform::Type::Win32W;
@@ -879,6 +905,17 @@ bool ImportProject::importVcxproj(const std::string &filename, std::map<std::str
879905
fs.defines += ";__AVX512__";
880906
additionalIncludePaths += ';' + i.additionalIncludePaths;
881907
}
908+
bool useUnicode = false;
909+
for (const ConfigurationPropertyGroup &c : configurationPropertyGroups) {
910+
if (!c.conditionIsTrue(p))
911+
continue;
912+
// in msbuild the last definition wins
913+
useUnicode = c.useUnicode;
914+
fs.useMfc = c.useOfMfc;
915+
}
916+
if (useUnicode) {
917+
fs.defines += ";UNICODE=1;_UNICODE=1";
918+
}
882919
fsSetDefines(fs, fs.defines);
883920
fsSetIncludePaths(fs, Path::getPathFromFilename(filename), toStringList(includePath + ';' + additionalIncludePaths), variables);
884921
for (const auto &path : sharedItemsIncludePaths) {

lib/importproject.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ namespace cppcheck {
5252
* @brief Importing project settings.
5353
*/
5454
class CPPCHECKLIB WARN_UNUSED ImportProject {
55+
friend class TestImporter;
5556
public:
5657
enum class Type : std::uint8_t {
5758
NONE,
@@ -111,6 +112,7 @@ class CPPCHECKLIB WARN_UNUSED ImportProject {
111112
bool importSln(std::istream &istr, const std::string &path, const std::vector<std::string> &fileFilters);
112113
static SharedItemsProject importVcxitems(const std::string &filename, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache);
113114
bool importVcxproj(const std::string &filename, std::map<std::string, std::string, cppcheck::stricmp> &variables, const std::string &additionalIncludeDirectories, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache);
115+
bool importVcxproj(const std::string &filename, const tinyxml2::XMLDocument &doc, std::map<std::string, std::string, cppcheck::stricmp> &variables, const std::string &additionalIncludeDirectories, const std::vector<std::string> &fileFilters, std::vector<SharedItemsProject> &cache);
114116
bool importBcb6Prj(const std::string &projectFilename);
115117

116118
static void printError(const std::string &message);

test/testimportproject.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "redirect.h"
2323
#include "settings.h"
2424
#include "suppressions.h"
25+
#include "xml.h"
2526

2627
#include <list>
2728
#include <map>
@@ -34,6 +35,8 @@ class TestImporter : public ImportProject {
3435
public:
3536
using ImportProject::importCompileCommands;
3637
using ImportProject::importCppcheckGuiProject;
38+
using ImportProject::importVcxproj;
39+
using ImportProject::SharedItemsProject;
3740

3841
bool sourceFileExists(const std::string & /*file*/) override {
3942
return true;
@@ -71,6 +74,7 @@ class TestImportProject : public TestFixture {
7174
TEST_CASE(importCompileCommandsDirectoryInvalid); // 'directory' field not a string
7275
TEST_CASE(importCppcheckGuiProject);
7376
TEST_CASE(ignorePaths);
77+
TEST_CASE(testVcxprojUnicode);
7478
}
7579

7680
void setDefines() const {
@@ -455,6 +459,59 @@ class TestImportProject : public TestFixture {
455459
ASSERT_EQUALS(0, project.fileSettings.size());
456460
}
457461

462+
void testVcxprojUnicode() const
463+
{
464+
const char vcxproj[] = R"-(
465+
<?xml version="1.0" encoding="utf-8"?>
466+
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
467+
<ItemGroup Label="ProjectConfigurations">
468+
<ProjectConfiguration Include="Debug|Win32">
469+
<Configuration>Debug</Configuration>
470+
<Platform>Win32</Platform>
471+
</ProjectConfiguration>
472+
<ProjectConfiguration Include="Release|Win32">
473+
<Configuration>Release</Configuration>
474+
<Platform>Win32</Platform>
475+
</ProjectConfiguration>
476+
</ItemGroup>
477+
<PropertyGroup Label="Configuration">
478+
<!-- Only to test that the last configuration entry overwrites this -->
479+
<CharacterSet>Unicode</CharacterSet>
480+
</PropertyGroup>
481+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
482+
<ConfigurationType>Application</ConfigurationType>
483+
<UseDebugLibraries>true</UseDebugLibraries>
484+
<PlatformToolset>v143</PlatformToolset>
485+
<CharacterSet>Unicode</CharacterSet>
486+
</PropertyGroup>
487+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
488+
<ConfigurationType>Application</ConfigurationType>
489+
<UseDebugLibraries>false</UseDebugLibraries>
490+
<PlatformToolset>v143</PlatformToolset>
491+
<CharacterSet>NotSet</CharacterSet>
492+
<UseOfMfc>Static</UseOfMfc>
493+
</PropertyGroup>
494+
<ItemGroup>
495+
<ClCompile Include="main.cpp" />
496+
</ItemGroup>
497+
</Project>
498+
)-";
499+
tinyxml2::XMLDocument doc;
500+
ASSERT_EQUALS(tinyxml2::XML_SUCCESS, doc.Parse(vcxproj, sizeof(vcxproj)));
501+
TestImporter project;
502+
std::map<std::string, std::string, cppcheck::stricmp> variables;
503+
std::vector<TestImporter::SharedItemsProject> cache;
504+
ASSERT_EQUALS(project.importVcxproj("test.vcxproj", doc, variables, {}, {}, cache), true);
505+
ASSERT_EQUALS(project.fileSettings.size(), 2);
506+
ASSERT(project.fileSettings.front().defines.find(";UNICODE=1;") != std::string::npos);
507+
ASSERT(project.fileSettings.front().defines.find(";_UNICODE=1") != std::string::npos);
508+
ASSERT(project.fileSettings.front().defines.find(";_UNICODE=1;") == std::string::npos); // No duplicates
509+
ASSERT_EQUALS(project.fileSettings.front().useMfc, false);
510+
ASSERT(project.fileSettings.back().defines.find(";UNICODE=1;") == std::string::npos);
511+
ASSERT(project.fileSettings.back().defines.find(";_UNICODE=1") == std::string::npos);
512+
ASSERT_EQUALS(project.fileSettings.back().useMfc, true);
513+
}
514+
458515
// TODO: test fsParseCommand()
459516

460517
// TODO: test vcxproj conditions

0 commit comments

Comments
 (0)