[Arduino] Making sense of compiler errors

ArduinoJson makes intensive use of templates and whenever there is an error, the compiler produces a long output that is very hard to digest.

In particular, it’s very difficult to see where the error is located. Is it in your code? Or is it in the library? The compiler clearly shows that the error happened in the code of the library, but is this really a bug in ArduinoJson?

If you ever got in this situation, this article is for you! We’ll see how to decipher the compiler output and get to the root of the error.

Our example

This blog post is inspired by an issue submitted by John D. Allen:

Getting a number of errors when trying to use the library on a Adafruit Feather M0 (ATSAMD21G18 ARM Cortex M0 processor):

In file included from D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonVariantBase.hpp:10:0,
   
                 from D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonVariant.hpp:16,
   
                 from D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonBuffer.hpp:15,
   
                 from D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\JsonParser.hpp:10,
   
                 from D:\libraries\ArduinoJson\src\ArduinoJson\JsonBufferBase.hpp:10,
   
                 from D:\libraries\ArduinoJson\src\ArduinoJson\DynamicJsonBuffer.hpp:10,
   
                 from D:\libraries\ArduinoJson\src\ArduinoJson.hpp:10,
   
                 from D:\libraries\ArduinoJson\src\ArduinoJson.h:10,
   
                 from D:\MyProgram\MyProgram.ino:7:
   
D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonVariantCasts.hpp: In instantiation of 'ArduinoJson::JsonVariantCasts<TImpl>::operator T() const [with T = char*; TImpl = ArduinoJson::JsonObjectSubscript<const int&>]':
   
D:\MyProgram\MyProgram.ino:132:27:   required from here
   
D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonVariantCasts.hpp:52:35: error: invalid conversion from 'ArduinoJson::Internals::JsonVariantAs<char*>::type {aka const char*}' to 'char*' [-fpermissive]
   
     return impl()->template as<T>();
   
                                   ^
   
In file included from D:\libraries\ArduinoJson\src\ArduinoJson.hpp:12:0,
   
                 from D:\libraries\ArduinoJson\src\ArduinoJson.h:10,
   
                 from D:\MyProgram\MyProgram.ino:7:
   
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObject.hpp: In instantiation of 'ArduinoJson::Internals::List<ArduinoJson::JsonPair>::iterator ArduinoJson::JsonObject::findKey(TStringRef) [with TStringRef = const int&; ArduinoJson::Internals::List<ArduinoJson::JsonPair>::iterator = ArduinoJson::Internals::ListIterator<ArduinoJson::JsonPair>]':
   
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObject.hpp:305:66:   required from 'ArduinoJson::Internals::List<ArduinoJson::JsonPair>::const_iterator ArduinoJson::JsonObject::findKey(TStringRef) const [with TStringRef = const int&; ArduinoJson::Internals::List<ArduinoJson::JsonPair>::const_iterator = ArduinoJson::Internals::ListConstIterator<ArduinoJson::JsonPair>]'
   
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObject.hpp:311:48:   required from 'typename ArduinoJson::Internals::JsonVariantAs<TValue>::type ArduinoJson::JsonObject::get_impl(TStringRef) const [with TStringRef = const int&; TValue = char*; typename ArduinoJson::Internals::JsonVariantAs<TValue>::type = const char*]'
   
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObject.hpp:172:48:   required from 'typename ArduinoJson::TypeTraits::EnableIf<(! ArduinoJson::TypeTraits::IsArray<TString>::value), typename ArduinoJson::Internals::JsonVariantAs<T>::type>::type ArduinoJson::JsonObject::get(const TString&) const [with TValue = char*; TString = int; typename ArduinoJson::TypeTraits::EnableIf<(! ArduinoJson::TypeTraits::IsArray<TString>::value), typename ArduinoJson::Internals::JsonVariantAs<T>::type>::type = const char*]'
   
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObjectSubscript.hpp:64:36:   required from 'typename ArduinoJson::Internals::JsonVariantAs<T>::type ArduinoJson::JsonObjectSubscript<TKey>::as() const [with TValue = char*; TStringRef = const int&; typename ArduinoJson::Internals::JsonVariantAs<T>::type = const char*]'
   
D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonVariantCasts.hpp:52:35:   required from 'ArduinoJson::JsonVariantCasts<TImpl>::operator T() const [with T = char*; TImpl = ArduinoJson::JsonObjectSubscript<const int&>]'
   
D:\MyProgram\MyProgram.ino:132:27:   required from here
   
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObject.hpp:299:67: error: 'equals' is not a member of 'ArduinoJson::Internals::StringTraits<const int&, void>'
   
       if (Internals::StringTraits<TStringRef>::equals(key, it->key)) break;
   
                                                                   ^
   
exit status 1
Error compiling for board Adafruit Feather M0.

Since none of this actually references my code, I am assuming its an issue with the library and/or my environment.

This issue doesn’t seem to match any of the Known Issues in the FAQ from what I could see.

That is clearly a good issue, we can definitely tell that John took time to understand what’s going on. He analyzed the compiler output and legitimately supposed that there was a problem with the library.

Now, let’s see how with can make sense of this compiler gibberish.

How GCC presents errors

Most of the time, the compiler will produce more than one error or warning; it’s essential to treat them one by one.

Start by finding the first line where the word error: or warning: appears, because it contains the location where the first problem was detected. If there are errors and warnings, choose the first of all because a warning often gives clues to understand an error.

Have a look at the picture below, where I highlighted the two errors from the listing above:

Highlighted parts of compiler output

As you can see, each error contains many lines within three groups:

  1. the "inclusion stack," which shows how the file was included,
  2. the "instantiation stack," which shows how the type was instantiated,
  3. the actual error message.

You need to start your investigation from group C, as it contains the actual error message and shows where the error was detected. But this line shows the effect, not the cause, so we need to investigate further to find the cause.

The group B shows the steps that the compiler followed to build the erroneous line. It’s a stack of instantiation, and it’s very similar to the call stack that you see in a debugger. At the top of the stack is the most recent instantiation (the closest to the error), the second line is the instantiation that led to the first, etc. At the bottom of the stack is the original instantiation (the closest to the program), this is usually the root cause of the error.

Finally, group A shows how the erroneous file was included in the program. It shows which file included which, up to the root .cpp or .ino. It is rarely useful.

The first error in our example

When the output is complex, it can be useful to copy the content in a powerful text editor (I use Sublime Text). From there, we can remove the noise (long file path and namespace) and align the output to make it more readable.

Here is our first error (group C) after simplifying file path and namespaces:

JsonVariantCasts.hpp:52:35: error: invalid conversion from
  'JsonVariantAs<char*>::type {aka const char*}' to 'char*' [-fpermissive]

This line tells that the compiler is unhappy to convert a const char* into a char*. Indeed this conversion is forbidden because it would lose the constness of the pointer. This error appears in ArduinoJson’s source code, but it’s the effect, not the cause, we need to look at the instantiation stack (group B) to find the cause:

JsonVariantCasts.hpp: In instantiation of
    'JsonVariantCasts<TImpl>::operator T() const [with T = char*]':
MyProgram.ino:132:27:     required from here

It’s usually easier to start at the bottom of the stack because it points to a line in the calling program. In this case, the line 132 of MyProgram.ino contains:

char* value = obj[key];

Indeed, this line contains an error: JsonObject returns strings as const char*, not char*, hence the invalid conversion. We can fix this error by adding const in front of char*:

const char* value = obj[key];

This error was easy to find, no need to unroll the instantiation stack. Let’s see the second.

The second error in our example

The second error message (group C) is:

JsonObject.hpp:299:error: 'equals' is not a member of 'StringTraits<const int&>'
    if (StringTraits<TStringRef>::equals(key, it->key)) break;

This line says that compiler is looking for a member named equals in the type StringTraits<const int&>, but this member doesn’t exist. As the author of the library, I know what it means, but it’s probably not your case. To understand what is happening, we need to look at the instantiation stack (group B):

JsonObject.hpp:     In instantiation of 'ArduinoJson::Internals::List<Arduino...
JsonObject.hpp:305:         required from 'ArduinoJson::Internals::List<JsonP...
JsonObject.hpp:311:         required from 'typename ArduinoJson::Internals::J...
JsonObject.hpp:172:         required from 'typename ArduinoJson::TypeTraits::...
JsonObjectSubscript.hpp:64: required from 'typename ArduinoJson::Internals::J...
JsonVariantCasts.hpp:52:    required from 'ArduinoJson::JsonVariantCasts<TImp...
MyProgram.ino:132:          required from here

The bottom of the stack is the same as for the first error, meaning that the same line generated two compiler errors. We already looked at this line, and it’s not apparent why it has any relation to StringTraits<const int&>. To understand further, we need to unroll the stack: start from the bottom and climb up one line after the other.

The next instantiation (second from the bottom of group B) is:

JsonVariantCasts.hpp:52: required from
  'ArduinoJson::JsonVariantCasts<TImpl>::operator T() const
    [with T = char*;
          TImpl = ArduinoJson::JsonObjectSubscript<const int&>]'

Again, the meaning is probably very obscure to you, so let’s move to the next instantiation (third from the bottom):

JsonObjectSubscript.hpp:64: required from
  'typename ArduinoJson::Internals::JsonVariantAs<T>::type
    ArduinoJson::JsonObjectSubscript<TKey>::as() const
      [with TValue = char*;
            TStringRef = const int&;
            ArduinoJson::Internals::JsonVariantAs<T>::type = const char*]'

This line starts to make sense. First, we see the char* and const char* that were causing the first error. Then, we see something very suspicious: TStringRef = const int&, which means that a template type named TStringRef has been set to const int&. From the name TStringRef, we can infer that a string reference is expected, but it is currently set to an int reference.

Let’s look at the next instantiation (fourth from the bottom):

JsonObject.hpp:172: required from
  'typename ArduinoJson::TypeTraits::EnableIf<
    (! ArduinoJson::TypeTraits::IsArray<TString>::value),
    typename ArduinoJson::Internals::JsonVariantAs<T>::type>::type
  ArduinoJson::JsonObject::get(const TString&) const
    [with TValue = char*;
          TString = int;
          ArduinoJson::Internals::JsonVariantAs<T>::type>::type = const char*]'

This line is very long, please focus on the middle part, where we see a call to JsonObject::get(). Let’s go back to our original program; here is line 132 of MyProgram.ino:

char* value = obj[key];

During the compilation phase, this statement was transformed into the following:

char* value = obj.get<char*>(key);

The transformation occurred in the two first instantiations: JsonVariantCasts.hpp:52 and JsonObjectSubscript.hpp:64.

But this call to JsonObject::get(const TString&) is problematic. Indeed, the compiler says TString = int, which means the template type TString was deduced as int, whereas a string is expected.

The bug is now right before our eyes: the variable key is not a string, it’s an int!

Indeed a JsonObject is indexed by a string, and ArduinoJson doesn’t support accessing a key-value pair by its index. To fix this error, we must change the type of the variable key to one of the supported string types: const char*, String

Conclusion

In this article, we saw how GCC (the compiler used by Arduino) presents the errors: there is the error message, the instantiation stack, and the inclusion stack.

We saw that to understand what caused the error, we need to start with the error and unroll the instantiation stack until it makes sense.

For a seasoned C++ programmers, this was a piece of cake. But for a programmer that has never been in contact with templates, this can be a lot to swallow. Don’t worry, this will pass quickly, after one or two analysis of this kind, it’ll become a second nature :-)

Want to learn more?

If you liked this article, you should check out my book Mastering ArduinoJson; it explains everything there is to know about the library and a lot more.

In fact, this article is a section from the “Troubleshooting” chapter, but there are a lot of other C++ related stuff. In particular, the book begins with a quick C++ course; this chapter will refresh you with pointers, references, and memory management, which are very common sources of pain among Arduino developers.