Understanding reference counting

300 views
Skip to first unread message

Ertan Küçükoglu

unread,
Jan 15, 2019, 2:03:30 PM1/15/19
to Jansson users
Hello,

First of all thank you to all who has helped Jansson in a way or other. I find it very useful.

I am new to this group, Jansson, and not good at C language. Once I read that Jansson library has automatic reference counting I was happy. I do not want to leak memory at all. I am developing some application for an embedded system. I would like to understand how reference counting of Jansson works.

I have a code similar to below (tried to keep is as small as possible):

 json_t *root;
 json_t
*result;
 json_t
*date;
 json_t
*time;
 json_error_t error
;
 json_t
*api_key;
 
const char *message_text;
 
char TempBuf[1024];
 
struct DATE_USER dt;
 
struct TIME_USER tm;

 root
= json_loads(TempBuf, 0, &error);
 
if(root){
   date
= json_object_get(root, "date");
   
if(date){
     message_text
= json_string_value(date);
     memset
(TempBuf, 0, sizeof(TempBuf));
     strncpy
(TempBuf, message_text, 4);
     dt
.year = atoi(TempBuf);

     memset
(TempBuf, 0, sizeof(TempBuf));
     strncpy
(TempBuf, message_text+4, 2);
     dt
.mon = atoi(TempBuf);

     memset
(TempBuf, 0, sizeof(TempBuf));
     strncpy
(TempBuf, message_text+6, 2);
     dt
.day = atoi(TempBuf);
   
}

   time
= json_object_get(root, "time");
   
if(time){
     message_text
= json_string_value(time);
     memset
(TempBuf, 0, sizeof(TempBuf));
     strncpy
(TempBuf, message_text, 2);
     tm
.hour= atoi(TempBuf);

     memset
(TempBuf, 0, sizeof(TempBuf));
     strncpy
(TempBuf, message_text+2, 2);
     tm
.min = atoi(TempBuf);

     memset
(TempBuf, 0, sizeof(TempBuf));
     strncpy
(TempBuf, message_text+4, 2);
     tm
.sec = atoi(TempBuf);
   
}

   json_decref
(date);
   json_decref
(time);
   ret
= SetTime_Api(&dt, &tm);
   
if(ret != 0){
     
// no error check here
   
}
 
   result
= json_object_get(root, "result");
   
if(result){
     message_text
= json_string_value(result);
     
if(memcmp(message_text, "success", 7) == 0){
       api_key
= json_object_get(root, "api_key");
       
if(api_key){
         message_text
= json_string_value(api_key);
         
WriteFile_Api("S:\\api_key.txt", (u8*)message_text, 0, strlen(message_text));
         
if(KeyBufLen > (int)strlen(message_text)){
           strcpy
(KeyBuf, message_text);
           json_decref
(root);
           
return 0;
         
}else{
           json_decref
(root);
           
return -10;  // buffer size not enough
         
}
       
}
     
}
   
}
   json_decref
(root);
 
}


My questions are:
1- Is it enough to call one time json_decref(root) before exiting my own function and that single call will clear all references (date, time, result api_key, message_text) and free all allocated memory?
2- I wonder if I am leaking memory in above sample code for "message_text" because I am using it for more than one json_string_value()?

Thanks & regards,
Ertan Küçükoğlu

Ertan Küçükoglu

unread,
Jan 15, 2019, 3:50:43 PM1/15/19
to Jansson users
For the record. I receive json string like following one.

{"date":"20190115","time":"202851","result":"success","api_key":"6b1b06c0391b5ea2620b7d84004ef169"}



Thanks again.

Graeme Smecher

unread,
Jan 15, 2019, 5:13:55 PM1/15/19
to jansso...@googlegroups.com
Hi Ertan,
Since json_object_get() returns a borrowed reference, these
json_decref() calls are incorrect. You should remove them.

>    ret =SetTime_Api(&dt,&tm);
>    if(ret !=0){
>      // no error check here
>    }
>  
>    result =json_object_get(root,"result");
>    if(result){
>      message_text =json_string_value(result);
>      if(memcmp(message_text,"success",7)==0){
>        api_key =json_object_get(root,"api_key");
>        if(api_key){
>          message_text =json_string_value(api_key);
>        
>  WriteFile_Api("S:\\api_key.txt",(u8*)message_text,0,strlen(message_text));
>          if(KeyBufLen>(int)strlen(message_text)){
>            strcpy(KeyBuf,message_text);
>            json_decref(root);
>            return0;
>          }else{
>            json_decref(root);
>            return-10; // buffer size not enough
>          }
>        }
>      }
>    }
>    json_decref(root);

You call json_decref(root) twice here: once at this line, and once
inside the if/else statement above. Only one of these calls is correct.
I recommend you remove the ones inside the if/else statement, since you
are not guaranteed to execute those lines if your input structure is
malformed.

>  }
> |
>
>
> My questions are:
> 1- Is it enough to call one time json_decref(root) before exiting my own
> function and that single call will clear all references (date, time,
> result api_key, message_text) and free all allocated memory?

Yes, this is the right approach. A single json_decref() on root should
free the root object and everything it contains (including the borrowed
references you obtained with json_object_get, and the string pointers
you obtaind with json_string_value.) This will help keep your code
readable and simple.

> 2- I wonder if I am leaking memory in above sample code for
> "message_text" because I am using it for more than one json_string_value()?

No, you aren't leaking memory there. The pointer returned by
json_string_value() is not "yours": you can't safely modify or free it,
and you can only access the string it points to while the underlying
json_t object (that really owns it!) still exists.

As usual, I recommend you try the "valgrind" tool. It will tell you
fairly accurately if you missed a json_decref() call, or if you have
memory corruption somewhere.

best,
Graeme
pEpkey.asc

Ertan Küçükoglu

unread,
Jan 16, 2019, 1:57:31 AM1/16/19
to Jansson users
Hello,

Thank you for explanations and suggestion to use Valgrind. My case, I cannot use Valgrind because I do not have opportunity to run generated executable on linux, windows or else where. It can only be used on embedded device itself.

My initial post was to read json string. I also build json strings. Some of them has json arrays and I do loops. I also wonder if there is any leak in my below code:


 json_t *root;
 json_t
*transactions;
 json_t
*tmp;
 
char *json_result;


transactions
= json_array();
i
= 0;
while(1)
{
 
if(i > 0)
 
{
   
//   if max transmit count reached or final packet
   
if ((i % MAX_RECORDS_IN_SINGLE_TRANSFER == 0) || (i == TotalTransactions))
   
{
      root
= json_object();

      json_object_set_new
(root, "ApiKey", json_string(ApiKey));
      json_object_set_new
(root, "ttc", json_integer(TotalTransactions));

      json_object_set_new
(root, "data", transactions);

      json_result
= json_dumps(root, JSON_COMPACT);

      transactions
= json_array();

     
if(i == TotalTransactions)
       
goto GoOut;
   
}
 
}

  tmp
= json_object();

  memset
(buf, 0, sizeof(buf));
  strncpy
(buf, tr.TransactionCount, sizeof(tr.TransactionCount));
  json_object_set_new
(tmp, "tc", json_string(trim(buf)));

  memset
(buf, 0, sizeof(buf));
  strncpy
(buf, tr.TransactionDate, sizeof(tr.TransactionDate));
  json_object_set_new
(tmp, "td", json_string(trim(buf)));

  json_array_append_new
(transactions, tmp);
 
  i
++;
}


GoOut:
  json_decref
(root);
  json_decref
(tmp);


I am worried about "root, tmp, transaction" obviously. I do not know if using json_object_set_new() moves all data and memory clean up responsibilities to root as in my initial case.

Graeme Smecher

unread,
Jan 16, 2019, 11:57:38 AM1/16/19
to jansso...@googlegroups.com
Hi Ertan,

On 2019-01-15 10:57 p.m., Ertan Küçükoglu wrote:
> Hello,
>
> Thank you for explanations and suggestion to use Valgrind. My case, I
> cannot use Valgrind because I do not have opportunity to run generated
> executable on linux, windows or else where. It can only be used on
> embedded device itself.

That's not really true: I've been editing your code samples slightly and
running them on my Linux PC, with valgrind.

> My initial post was to read json string. I also build json strings. Some
> of them has json arrays and I do loops. I also wonder if there is any
> leak in my below code:
>
>
> |
>  json_t *root;
>  json_t *transactions;
>  json_t *tmp;
>  char*json_result;
>
>
> transactions =json_array();
> i =0;
> while(1)
> {
>   if(i >0)
>   {
>     //   if max transmit count reached or final packet
>     if((i %MAX_RECORDS_IN_SINGLE_TRANSFER ==0)||(i ==TotalTransactions))
>     {
>       root =json_object();

Here, you create a new "root" object that needs to (eventually) be
json_decref'd. Since this occurs in a loop, you will probably need more
than one call to json_decref(root). However, you have only one call at
the bottom of the function.

>       json_object_set_new(root,"ApiKey",json_string(ApiKey));
>       json_object_set_new(root,"ttc",json_integer(TotalTransactions));
>
>       json_object_set_new(root,"data",transactions);

For this kind of code, json_pack() is much more compact. You could use
something like:

json_object_set_new(root,
json_pack("s:[s:s,s:i]", "data",
"ApiKey", ApiKey, "ttc", TotalTransactions));

>       json_result =json_dumps(root,JSON_COMPACT);

This seems like the last place you use "root" in the loop, and would be
a good place to consider json_decref(root).

>       transactions =json_array();

Here, you create a new "transactions" object -- but you only use it if
you enter the next loop iteration. It's easy to leak a single instance
this way, even if your reference counting is otherewise correct. Please
consider initializing "transactions" just before you use it, rather than
at the end of the previous loop iteration.

>
>       if(i ==TotalTransactions)
>         goto GoOut;
>     }
>   }
>
>   tmp =json_object();
>
>   memset(buf,0,sizeof(buf));
>   strncpy(buf,tr.TransactionCount,sizeof(tr.TransactionCount));
>   json_object_set_new(tmp,"tc",json_string(trim(buf)));
>
>   memset(buf,0,sizeof(buf));
>   strncpy(buf,tr.TransactionDate,sizeof(tr.TransactionDate));
>   json_object_set_new(tmp,"td",json_string(trim(buf)));
>
>   json_array_append_new(transactions,tmp);

Again, json_pack() can simplify this kind of code.

>  
>   i++;
> }
>
>
> GoOut:
>   json_decref(root);
>   json_decref(tmp);
>
> |
> I am worried about "root, tmp, transaction" obviously. I do not know if
> using json_object_set_new() moves all data and memory clean up
> responsibilities to root as in my initial case.

As the documentation claims, "json_object_set_new()" steals a reference
to the object, so you are transferring ownership of the object to its
new container. You need only decref() the container to free up memory.

I do embedded work, too, and occasionally use mock-ups or other testing
techniques to allow me to run, profile, and test embedded code on a
Linux PC. There is no replacement for the confidence that a proper test
environment gives you. (As a side perk, you will become less reliant on
free code reviews from strangers on the Internet!)

best,
Graeme
pEpkey.asc
Reply all
Reply to author
Forward
0 new messages