@@ -29,7 +29,8 @@ class TestSarif : public TestFixture
2929{
3030public:
3131 TestSarif () : TestFixture(" TestSarif" )
32- {}
32+ {
33+ }
3334
3435private:
3536 // Shared test code with various error types
@@ -245,6 +246,7 @@ int main() {
245246 TEST_CASE (sarifSecuritySeverity);
246247 TEST_CASE (sarifLocationInfo);
247248 TEST_CASE (sarifGenericDescriptions);
249+ TEST_CASE (sarifInstanceSpecificMessages);
248250 TEST_CASE (sarifCweTags);
249251 TEST_CASE (sarifRuleCoverage);
250252 TEST_CASE (sarifSeverityLevels);
@@ -406,17 +408,37 @@ int main() {
406408
407409 ASSERT (results.size () > 0 );
408410
409- // Check that we have different severity levels
410- bool hasError = false ;
411+ // Check that we have different severity levels and meaningful messages
412+ bool hasError = false ;
413+ bool hasNonEmptyMessage = false ;
414+
411415 for (const auto & result : results)
412416 {
413417 const picojson::object& res = result.get <picojson::object>();
414418 const std::string level = res.at (" level" ).get <std::string>();
415419 if (level == " error" )
416420 hasError = true ;
421+
422+ // Verify each result has a meaningful message
423+ ASSERT (res.find (" message" ) != res.end ());
424+ const picojson::object& message = res.at (" message" ).get <picojson::object>();
425+ ASSERT (message.find (" text" ) != message.end ());
426+ const std::string messageText = message.at (" text" ).get <std::string>();
427+
428+ // Messages should be non-empty and meaningful
429+ ASSERT (messageText.length () > 0 );
430+ if (messageText.length () > 5 ) // Reasonable minimum for a meaningful message
431+ {
432+ hasNonEmptyMessage = true ;
433+ }
434+
435+ // Basic validation that messages don't contain obvious placeholders
436+ ASSERT_EQUALS (std::string::npos, messageText.find (" {{" ));
437+ ASSERT_EQUALS (std::string::npos, messageText.find (" }}" ));
417438 }
418439
419440 ASSERT_EQUALS (true , hasError);
441+ ASSERT_EQUALS (true , hasNonEmptyMessage);
420442 }
421443
422444 void sarifRuleDescriptions ()
@@ -607,6 +629,140 @@ int main() {
607629 }
608630 }
609631
632+ void sarifInstanceSpecificMessages ()
633+ {
634+ // Use the global testCode to validate instance-specific messages
635+ const std::string sarif = runCppcheckSarif (testCode);
636+
637+ std::string errorMsg;
638+ ASSERT_EQUALS (true , validateSarifJson (sarif, errorMsg));
639+
640+ picojson::value json;
641+ picojson::parse (json, sarif);
642+ const picojson::object& root = json.get <picojson::object>();
643+ const picojson::array& runs = root.at (" runs" ).get <picojson::array>();
644+ const picojson::object& run = runs[0 ].get <picojson::object>();
645+ const picojson::array& results = run.at (" results" ).get <picojson::array>();
646+
647+ ASSERT (results.size () > 0 );
648+
649+ // Validate that results contain instance-specific information
650+ bool foundArrayBoundsWithSpecifics = false ;
651+ bool foundAnyInstanceSpecificMessage = false ;
652+
653+ for (const auto & result : results)
654+ {
655+ const picojson::object& res = result.get <picojson::object>();
656+ const std::string ruleId = res.at (" ruleId" ).get <std::string>();
657+ const picojson::object& message = res.at (" message" ).get <picojson::object>();
658+ const std::string messageText = message.at (" text" ).get <std::string>();
659+
660+ // Skip system include warnings as they're not relevant to our test
661+ if (ruleId == " missingIncludeSystem" )
662+ continue ;
663+
664+ if (ruleId == " arrayIndexOutOfBounds" )
665+ {
666+ // Should contain specific array name and/or index from testCode
667+ if ((messageText.find (" array" ) != std::string::npos && messageText.find (" 10" ) != std::string::npos) ||
668+ (messageText.find (" Array" ) != std::string::npos && messageText.find (" 10" ) != std::string::npos))
669+ {
670+ foundArrayBoundsWithSpecifics = true ;
671+ foundAnyInstanceSpecificMessage = true ;
672+ // Verify it's about array bounds
673+ ASSERT (messageText.find (" bound" ) != std::string::npos ||
674+ messageText.find (" index" ) != std::string::npos ||
675+ messageText.find (" Array" ) != std::string::npos);
676+ }
677+ }
678+ else if (ruleId == " nullPointer" )
679+ {
680+ // Should contain the specific variable name from our test (ptr)
681+ if (messageText.find (" ptr" ) != std::string::npos)
682+ {
683+ foundAnyInstanceSpecificMessage = true ;
684+ // Verify it's a meaningful message about null pointer dereference
685+ ASSERT (messageText.find (" null" ) != std::string::npos ||
686+ messageText.find (" nullptr" ) != std::string::npos ||
687+ messageText.find (" NULL" ) != std::string::npos ||
688+ messageText.find (" Null" ) != std::string::npos);
689+ }
690+ }
691+ else if (ruleId == " memleak" )
692+ {
693+ // Should contain the specific variable name from testCode (mem)
694+ if (messageText.find (" mem" ) != std::string::npos)
695+ {
696+ foundAnyInstanceSpecificMessage = true ;
697+ // Verify it's about memory leak
698+ ASSERT (messageText.find (" leak" ) != std::string::npos ||
699+ messageText.find (" free" ) != std::string::npos ||
700+ messageText.find (" memory" ) != std::string::npos);
701+ }
702+ }
703+ else if (ruleId == " uninitvar" )
704+ {
705+ // Should contain the specific variable name from testCode (x)
706+ if (messageText.find (" 'x'" ) != std::string::npos)
707+ {
708+ foundAnyInstanceSpecificMessage = true ;
709+ // Verify it's about uninitialized variable
710+ ASSERT (messageText.find (" uninit" ) != std::string::npos ||
711+ messageText.find (" initial" ) != std::string::npos);
712+ }
713+ }
714+ else if (ruleId == " unusedVariable" )
715+ {
716+ // Should contain specific variable names from testCode (unused, redundant, etc.)
717+ if (messageText.find (" unused" ) != std::string::npos ||
718+ messageText.find (" redundant" ) != std::string::npos ||
719+ messageText.find (" counter" ) != std::string::npos)
720+ {
721+ foundAnyInstanceSpecificMessage = true ;
722+ // Verify it's about unused variable
723+ ASSERT (messageText.find (" unused" ) != std::string::npos ||
724+ messageText.find (" never used" ) != std::string::npos);
725+ }
726+ }
727+ else if (ruleId == " doubleFree" )
728+ {
729+ // Should contain specific variable name from testCode (p)
730+ if (messageText.find (" 'p'" ) != std::string::npos)
731+ {
732+ foundAnyInstanceSpecificMessage = true ;
733+ // Verify it's about double free
734+ ASSERT (messageText.find (" free" ) != std::string::npos ||
735+ messageText.find (" deallocat" ) != std::string::npos);
736+ }
737+ }
738+ else if (ruleId == " redundantAssignment" )
739+ {
740+ // Should contain specific variable name from testCode (redundant)
741+ if (messageText.find (" redundant" ) != std::string::npos)
742+ {
743+ foundAnyInstanceSpecificMessage = true ;
744+ // Verify it's about redundant assignment
745+ ASSERT (messageText.find (" redundant" ) != std::string::npos ||
746+ messageText.find (" assign" ) != std::string::npos);
747+ }
748+ }
749+
750+ // Verify that all messages are non-empty and meaningful
751+ ASSERT (messageText.length () > 0 );
752+ ASSERT (messageText != " " );
753+
754+ // Messages should not contain generic placeholders
755+ ASSERT_EQUALS (std::string::npos, messageText.find (" {{" ));
756+ ASSERT_EQUALS (std::string::npos, messageText.find (" }}" ));
757+ }
758+
759+ // We must find at least the array bounds violation with specific details
760+ ASSERT_EQUALS (true , foundArrayBoundsWithSpecifics);
761+
762+ // We should find at least one instance-specific message overall
763+ ASSERT_EQUALS (true , foundAnyInstanceSpecificMessage);
764+ }
765+
610766 void sarifCweTags ()
611767 {
612768 const std::string sarif = runCppcheckSarif (testCode);
0 commit comments