bblanchon / ArduinoJson

📟 JSON library for Arduino and embedded C++. Simple and efficient.
https://arduinojson.org
MIT License
6.7k stars 1.12k forks source link

Passing JsonDocument by reference #2088

Closed jgroszko closed 4 months ago

jgroszko commented 4 months ago

Describe the issue
I'm trying to DRY up my code and create a helper function for fetching some JSON and filtering it, but it looks like my program is crashing when it returns from the helper function.

Troubleshooter report
Here is the report generated by the ArduinoJson Troubleshooter:

  1. The program uses ArduinoJson 7
  2. The issue happens at run time
  3. The issue concerns deserialization
  4. deserializeJson() returns Ok
  5. Program crashes
  6. The program crashes when reading a value from the JsonDocument
  7. Replacing pointer with a String doesn't solve the issue
  8. Program calls neither strcmp(), nor strcpy(), not printf()

Environment
Here is the environment that I'm using':

Reproduction
Here is a small snippet that demonstrate the problem.

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char* ssid = "...";
const char* password = "...";

bool filteredJsonGet(String &url, JsonDocument &result, JsonDocument &filter) {
  HTTPClient http;
  http.useHTTP10(true);
  http.begin(url);
  Serial.println("About to get");
  int httpCode = http.GET();
  Serial.println("Got");

  if(httpCode != 200) {
    Serial.print("GET failed ");
    Serial.print(httpCode);
    Serial.print(" - ");
    Serial.println(url);

    http.end();

    return false;
  } else {
    Serial.println("HTTP Success");
    DeserializationError error = deserializeJson(result, http.getStream(), DeserializationOption::Filter(filter));
    Serial.println("Deserialized json");

    http.end();

    if (error) {
      Serial.print("GET failed ");
      Serial.print(url);
      Serial.print(" - deserializeJson() failed: ");
      Serial.println(error.c_str());
      return false;
    }

    if (result.overflowed()) {
      Serial.print("GET failed ");
      Serial.print(url);
      Serial.println(" - Ran out of memory for JsonDocument");
      return false;
    }

    Serial.println("No json errors");
  }
}

void setup() {
  Serial.begin(115200);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  String url = "http://worldtimeapi.org/api/timezone/America/Chicago";

  JsonDocument filter;
  filter["unixtime"] = true;

  JsonDocument result;

  if(filteredJsonGet(url, result, filter)) {
    Serial.print("Success ");
    Serial.println(result["unixtime"].as<const char*>());
  } else {
    Serial.println("Fail");
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}

Program output
If relevant, include the program output.

Expected output:

About to get
Got
HTTP Success
Deserialized json
No json errors
Success - 123456

Actual output:

About to get
Got
HTTP Success
Deserialized json
No json errors

abort() was called at PC 0x4015775e on core 1

Backtrace: 0x400838bd:0x3ffb2040 0x4008ec51:0x3ffb2060 0x40093fc9:0x3ffb2080 0x4015775e:0x3ffb2100 0x400d6355:0x3ffb21a0 0x400dde3e:0x3ffb2290

ELF file SHA256: 7b27522cbd92f4eb

Decoding stack results
0x400838bd: panic_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/panic.c:408
0x4008ec51: esp_system_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/esp_system.c:137
0x40093fc9: abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/newlib/abort.c:46
0x4015775e: _Unwind_Resume at /Users/brnomac003/.gitlab-runner/builds/qR2TxTby/0/idf/crosstool-NG/.build/xtensa-esp32-elf/src/gcc/libgcc/unwind.inc:245
0x400d6355: setup() at /Users/john/Documents/Arduino/arduinojson_pass_by_reference/arduinojson_pass_by_reference.ino:59
0x400dde3e: loopTask(void*) at /Users/john/Library/Arduino15/packages/Inkplate_Boards/hardware/esp32/8.1.0/cores/esp32/main.cpp:42

Thanks!

bblanchon commented 4 months ago

Hi @jgroszko,

After making the following changes, I could successfully run your program on my ESP32.

  #include <WiFi.h>
  #include <HTTPClient.h>
  #include <ArduinoJson.h>

  const char* ssid = "...";
  const char* password = "...";

  bool filteredJsonGet(String &url, JsonDocument &result, JsonDocument &filter) {
    HTTPClient http;
    http.useHTTP10(true);
    http.begin(url);
    Serial.println("About to get");
    int httpCode = http.GET();
    Serial.println("Got");

    if(httpCode != 200) {
      Serial.print("GET failed ");
      Serial.print(httpCode);
      Serial.print(" - ");
      Serial.println(url);

      http.end();

      return false;
    } else {
      Serial.println("HTTP Success");
      DeserializationError error = deserializeJson(result, http.getStream(), DeserializationOption::Filter(filter));
      Serial.println("Deserialized json");

      http.end();

      if (error) {
        Serial.print("GET failed ");
        Serial.print(url);
        Serial.print(" - deserializeJson() failed: ");
        Serial.println(error.c_str());
        return false;
      }

      if (result.overflowed()) {
        Serial.print("GET failed ");
        Serial.print(url);
        Serial.println(" - Ran out of memory for JsonDocument");
        return false;
      }

      Serial.println("No json errors");
    }

+   return true;    
  }

  void setup() {
    Serial.begin(115200);

    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
    }

    String url = "http://worldtimeapi.org/api/timezone/America/Chicago";

    JsonDocument filter;
    filter["unixtime"] = true;

    JsonDocument result;

    if(filteredJsonGet(url, result, filter)) {
      Serial.print("Success ");
-     Serial.println(result["unixtime"].as<const char*>());
+     Serial.println(result["unixtime"].as<unsigned long>());
    } else {
      Serial.println("Fail");
    }
  }

  void loop() {
    // put your main code here, to run repeatedly:

  }

I tested with versions 2.0.14 and 2.0.16 of the Arduino Core for ESP32.

I first thought that the crash was due to calling Serial.println() with a null string, but after checking with the implementation, I confirmed that it is safe to pass a null char pointer. Besides, the original code was not crashing my device; it just didn't write anything after Success.

Best regards, Benoit

bblanchon commented 4 months ago

PS: I just saw in your crash dump that you are using the Inkplate core. I looked at the source code and found the same protection against null pointers passed to println().

jgroszko commented 4 months ago

Hey Benoit,

Thanks for the quick response! It was definitely the missing return statement, glad it was something simple. I was afraid it'd be some pointer nonsense that I was missing.

bblanchon commented 4 months ago

You're welcome, @jgroszko. Thank you for using ArduinoJson! Don't forget to cast a star to support the project :wink: