c++runtime-errorsigabrt

Code submission on SPOJ gives runtime error (SIGABRT)


I have done an exercise on SPOJ to practice advanced algorithms.


The Problem Statement is as follows:

Harish went to a supermarket to buy exactly ‘k’ kilograms apples for his ‘n’ friends. The supermarket was really weird. The pricing of items was very different. He went to the Apples section and enquired about the prices. The salesman gave him a card in which he found that the prices of apples were not per kg. The apples were packed into covers, each containing ‘x’ kg of apples, x > 0 and ‘x’ is an integer. An ‘x’ kg packet would be valued at ‘y’ rupees. So, the placard contained a table with an entry ‘y’ denoting the price of an ‘x’ kg packet. If ‘y’ is -1 it means that the corresponding packet is not available. Now as apples are available only in packets, he decides to buy at most ‘n’ packets for his ‘n’ friends i.e he will not buy more than n packets of apples. Harish likes his friends a lot and so he does not want to disappoint his friends. So now, he will tell you how many friends he has and you have to tell him the minimum amount of money he has to spend for his friends.


This is the code I used to solve the problem:

#include <algorithm>
#include <iostream>
#include <vector>

using std::cout;
using std::cin;
using std::vector;
using std::endl;

int MinValueOf(int a, int b)
{
    return (a < b) ? a : b;
}
int BuyingApple(vector<int> PriceaTag, int Friends, int KilogramsToBuy)
{
    vector<vector<int>> Table(Friends + 1, vector<int>(KilogramsToBuy + 1, 0));
    for(int i = 1; i <= Friends; i++)
    {
        for(int j = 0; j <= i; j++)
        {
            Table[i][j] = INT32_MAX;
            if(j == 0)
                Table[i][0] = 0;
            else if(PriceaTag[j] > 0)
                Table[i][j] = MinValueOf(Table[i][j], Table[i - 1][i - j] +  PriceaTag[j]);
        }
    }
    return (Table[Friends][KilogramsToBuy] == 0) ? -1 : Table[Friends][KilogramsToBuy];
}
int main()
{
    vector<int> Price;
    int Friends, Kilogram, t;
    cin >> t;
    for(int i = 0; i < t; i++)
    {
        cin >> Friends >> Kilogram;
        vector<int> Price(Kilogram + 1, 0);
        for(int i = 1; i <= Kilogram; i++)
        {
            cin >> Price[i];
        }
        cout << BuyingApple(Price, Friends, Price.size() - 1) << endl;
    }
    return 0;
}

I/O of the code is as follows:

The first line of input will contain the number of test cases, C. Each test case will contain two lines. The first line containing N and K, the number of friends he has and the amount of Apples in kilograms which he should buy. The second line contains K space separated integers in which the ith integer specifies the price of a 'i' kg apple packet. A value of -1 denotes that the corresponding packet is unavailable.

The output for each test case should be a single line containing the minimum amount of money he has to spend for his friends. Print -1 if it is not possible for him to satisfy his friends.


Constraints:

0 < N <= 100
0 < K <= 100
0 < price <= 1000


But as I submitted my code, I received a message SIGABRT runtime error although my code ran smoothly in both Windows compiler (G++ 14) and Linux Compiler (G++ Clang 9). I have tried to debug but I failed. What might be wrong?


Solution

  • Since this is a SPOJ question, and you're not given the test data, what you should do is to randomize the tests until you get a failure. That way, you may be able to get a sample case that fails. This is called fuzzing, and is a technique that can be used in your question.

    The following will work for the cases that cause segmentation faults, and in some cases, to verify if a given output matches the expected output. In other words, instead of trying to figure out the test data, let the computer generate the tests for you.

    The way you do this is to look at the constraints that the question gives you, and generate random data that fits the constraints. Since they are all integers, you can do this by using the <random> header, and using a uniform_int_distribution.

    Here is a sample of fuzzing the data using the following constraints for N, K, and the data for prices:

    Constraints:
    
    0 < N <= 100
    0 < K <= 100
    0 < price <= 1000
    

    OK, so given this information, we can take your exact code, remove the cin statements, and replace everything with randomized data that fit the constraints. In addition, we can test for an out-of-bounds access if we use at() to access the vectors in the function that causes the issue.

    Given all of this information we can start with changing main to produce random data that fit the constraints of the question:

    #include <random>
    #include <algorithm>
    #include <vector>
    //...
    int main()
    {
        // random number generator
        std::random_device rd;
        std::mt19937 gen(rd());
    
        // Prices will be distributed from -1 to 1000
        std::uniform_int_distribution<> distrib(-1, 1000);
    
        // N and K are distributed between 1 and 100  
        std::uniform_int_distribution<> distribNK(1, 100);
    
        // This one will be used if we need to replace 0 in the Price vector with 
        // a good value 
        std::uniform_int_distribution<> distribPos(1, 1000);
    
        // our variables
        int Friends;
        int Kilogram;
        vector<int> Price;
    
        // we will keep going until we see an out-of-range failure
        while (true)
        {
            try
            {
                // generate random N and K values
                Friends = distribNK(gen);
                Kilogram = distribNK(gen);
    
                // Set up the Price vector
                Price = std::vector<int>(Kilogram + 1, 0);
    
                // Generate all the prices
                std::generate(Price.begin() + 1, Price.end(), [&]() { return distrib(gen); });
    
                // Make sure we get rid of any 0 prices and replace them with a random value
                std::transform(Price.begin() + 1, Price.end(), Price.begin() + 1, [&](int n)
                    { if (n == 0) return distribPos(gen);  return n; });
    
                // Now test the function
                std::cout << BuyingApple(Price, Friends, Price.size() - 1) << std::endl;
            }
    
            catch (std::out_of_range& rError)
            {
                std::cout << rError.what() << "\n";
                std::cout << "The following tests cause an issue:\n\n";
                // The following tests cause an issue with an out-of-range.  See the data
                std::cout << "Friends = " << Friends << "\nK = " << Kilogram << "\nPrice data:\n";
                int i = 0;
                for (auto p : Price)
                {
                    std::cout << "[" << i << "]: " << p << "\n";
                    ++i;
                }
                return 0;
            }
        }
    }
    

    Given all of this, we can change the BuyingApple function by replacing [ ] with at():

    int BuyingApple(vector<int> PriceaTag, int Friends, int KilogramsToBuy)
    {
        vector<vector<int>> Table(Friends + 1, vector<int>(KilogramsToBuy + 1, 0));
        for (int i = 1; i <= Friends; i++)
        {
            for (int j = 0; j <= i; j++)
            {
                Table.at(i).at(j) = INT32_MAX;
                if (j == 0)
                    Table[i][0] = 0;
                else if (PriceaTag[j] > 0)
                    Table[i][j] = MinValueOf(Table[i][j], Table.at(i - 1).at(i - j) + PriceaTag.at(j));
            }
        }
        return (Table[Friends][KilogramsToBuy] == 0) ? -1 : Table[Friends][KilogramsToBuy];
    }
    

    Now we have an automatic case generator, and will catch and display any cases that could cause an issue with the vectors. Note that we keep looping forever until we get a test case that "crashes". We then output the crashed case and can now use those values to debug the issue.

    We used std::generate and std::transform as an illustration of how to populate a vector (or any sequence container your test uses), and how to specialize the test (like making sure that Price has no 0 values). Another SPOJ question may need other specializations, but hopefully you get the basic idea.

    Here is a Live Example.

    We see that a test case caused an out-of-range exception to be thrown. The main function has a try/catch to process this error, and we can see the data that caused the issue.


    So it seems that if we have more friends than apples, the issue occurs where we go out-of-bounds. I will not attempt to fix the issue, but you now have a test case where the input fails.

    In general, you can use this technique with many, if not most of the "online judge" sites if the site doesn't show you failing test cases.

    Edit: Updated the lambda in the std::transform to only replace 0 in the Price vector.


    Edit: Here is a random string fuzzer that can produce fuzzed string data.

    You can control the number of strings, the minimum and maximum size of each string, and the alphabet of characters that will be used when generating each string.

    #include <random>
    #include <string>
    #include <vector>
    #include <iostream>
    
    struct StringFuzzer
    { 
        unsigned int maxStrings;  // number of strings to produce
        unsigned int minSize;     // minimum size of a string
        unsigned int maxSize;     // maximum size of the string
        bool fixedSize;           // Use fixed size of strings or random
        std::string alphabet;     // string alphabet/dictionary to use
        
    public:
        StringFuzzer() : maxStrings(10), minSize(0), maxSize(10), fixedSize(true), alphabet("abcdefghijklmnopqrstuvwxyz")
        {}
        StringFuzzer& setMaxStrings(unsigned int val) { maxStrings = val; return *this; };
        StringFuzzer& setMinSize(unsigned int val) { minSize = val; return *this; };
        StringFuzzer& setMaxSize(unsigned int val) { maxSize = val; return *this; };
        StringFuzzer& setAlphabet(const std::string& val) { alphabet = val; return *this; };
        StringFuzzer& setFixedSize(bool fixedsize) { fixedSize = fixedsize; return *this; }
    
        std::vector<std::string> getFuzzData() const
        {
            // random number generator
            std::random_device rd;
            std::mt19937 gen(rd());
    
            // Number of strings produced will be between 1 and maxStrings
            std::uniform_int_distribution<> distribStrings(1, maxStrings);
    
            // string size will be distributed between min and max sizes
            std::uniform_int_distribution<> distribMinMax(minSize, maxSize);
    
            // Picks letter from dictionary
            std::uniform_int_distribution<> distribPos(0, alphabet.size() - 1);
    
            std::vector<std::string> ret;
    
            // generate random number of strings
            unsigned int numStrings = maxStrings;
            if ( !fixedSize)
               numStrings = distribStrings(gen);
               
            ret.resize(numStrings);
    
            for (unsigned int i = 0; i < numStrings; ++i)
            {
                std::string& backStr = ret[i];
                // Generate size of string
                unsigned strSize = distribMinMax(gen);
                for (unsigned j = 0; j < strSize; ++j)
                {
                    // generate each character and append to string
                    unsigned pickVal = distribPos(gen);
                    backStr += alphabet[pickVal];
                }
            }
            return ret;
        }
    };
    
    int main()
    {
        StringFuzzer info;
        auto v = info.getFuzzData();  // produces a vector of strings, ready to be used in tests
        info.setAlphabet("ABCDEFG").setMinSize(1);  // alphabet consists only of these characters, and we will not have any empty strings
        v = info.getFuzzData();  // now should be a vector of random strings with "ABCDEFG" characters
        for (auto s : v)
           std::cout << s << "\n";
    }