Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: build warning - cast from pointer to integer of different size #110

Merged
merged 65 commits into from
Nov 4, 2021

Conversation

ChromaticIsobar
Copy link
Contributor

See PR #92

zackp30 and others added 15 commits May 5, 2016 15:55
While libm can be linked on generating final executable, it's still
preferred to link it against the library itself to better represent the
dependency hierarchy.
Added support for MSVC and (rather coincidentally) C++98 by replacing
the decltype keyword and removing the corresponding preprocessor
checks.
To prevent warnings in modern compilers
Example (starting in the source directory):
  $ mkdir build
  $ cd build
  $ ../configure
  $ make

Make complains:
 ar rcs libjsonparser.a json.o
 ar: json.o: No such file or directory
 make: *** [Makefile:29: libjsonparser.a] Error 1
json.h: <inttypes.h> → <stdint.h>
int64_t is defined in <stdint.h>, no need for the full <inttypes.h>

json.h: <stdlib.h> → <stddef.h>
<stddef.h> is enough to define size_t

json.c: <stdlib.h>
Now that <stdlib.h> has been removed from json.h, add it to json.c for
malloc()/calloc() and free()
@LB--
Copy link
Member

LB-- commented Aug 10, 2021

Thanks! Unfortunately this may be more complicated than originally assumed. <stdint.h> isn't available until C99, and this library is targeting ANSI C, meaning that header won't always be available on all supported systems. Some other PRs are adjusting which headers are included in which places as well, such as #109, so perhaps once that change is merged it will be safe to rebase this and merge it.

@DimitriPapadopoulos
Copy link
Contributor

Note that the library already includes header <inttypes.h> if json_int_t is not defined:

#ifndef json_int_t
   #ifndef _MSC_VER
      #include <inttypes.h>

and <stdint.h> on Windows:

#ifdef _MSC_VER
   #ifndef _CRT_SECURE_NO_WARNINGS
      #define _CRT_SECURE_NO_WARNINGS
   #endif
   #include <stdint.h>

Both headers are C99, not ANSI-C. But yes, it's quite possible to define json_int_t outside the library, so I agree the library is still ANSI C, except for a couple platform-dependent portability tricks.

@DimitriPapadopoulos
Copy link
Contributor

DimitriPapadopoulos commented Aug 10, 2021

Note that function json_alloc expects unsigned long anyway:

static void * json_alloc (json_state * state, unsigned long size, int zero)

Eventually you shall cast to unsigned long.

This tells me something's wrong here. I might be missing something, but why would the size of the allocation be a multiple of the value of a pointer?

            values_size = sizeof (*value->u.object.values) * value->u.object.length;

            if (! (value->u.object.values = (json_object_entry *) json_alloc
                  (state, values_size + ((unsigned long) value->u.object.values), 0)) )

It should be a multiple of the size of a pointer! For example, for array we simply have:

            if (! (value->u.array.values = (json_value **) json_alloc
               (state, value->u.array.length * sizeof (json_value *), 0)) )

@jamesamcl
Copy link
Collaborator

It's because there is something hacky in there. In the first pass, the values pointer field is used as an integer to store the length of all of the object names together. You can see this on line 423:

                 if (state.first_pass)
                    (*(json_char **) &top->u.object.values) += string_length + 1;
                 else
                 {  
                    top->u.object.values [top->u.object.length].name
                       = (json_char *) top->_reserved.object_mem;

                    top->u.object.values [top->u.object.length].name_length
                       = string_length;

                    (*(json_char **) &top->_reserved.object_mem) += string_length + 1;
                 }

Weirdly it casts it to a json_char* to add to it rather than an unsigned long or a size_t. I guess that's just a mistake. Then, in the second pass, it interprets it as an unsigned long to allocate enough space for all of the names together, in the fragment of code you found:

        values_size = sizeof (*value->u.object.values) * value->u.object.length;

        if (! (value->u.object.values = (json_object_entry *) json_alloc


              (state, values_size ///// <-- enough memory for the json_value objects


                 +


             ((unsigned long) value->u.object.values ///// <-- enough memory for the names of the objects


          ), 0)) )

The explicit way to describe what is happening would be with a union:

union {
    json_object_entry *values;
    size_t _reserved_names_len;
}

I guess I didn't do that because in ANSI C unions have to be named, so json->u.object.values would have to become json->u.object.u.values or something.

So to fix this I think the options would be:

  1. Make it a union in json_value - makes the public API ugly
  2. Add another reserved field to json_value for the names length. This would increase the size of every json_value by 1 pointer.
  3. Make a json_value_internal defined in json.c which is binary compatible with the public json_value, but with the union.
  4. Create a macro for reinterpreting the pointer field as a size
  5. Keep the kludge and fix the casting somehow

I think option 3 is the cleanest? But any suggestions welcome.

@DimitriPapadopoulos
Copy link
Contributor

DimitriPapadopoulos commented Aug 11, 2021

Thanks for the explanation. It's probably difficult to totally remove the warning when retrieving an integer from a larger pointer, unless a union is used to store the different types.

  • I do not find 1 that bad, especially since you have been using the very neat (short) name u for unions. But it does expose a field that is used only internally, if I understand you correctly.
  • Option 5 looks good, but I have no clue how to achieve it.
  • Option 4 probably won't help, a macro cannot remove a warning that option 5 cannot remove.
  • If the fields of json_value are not used at the same time, option 2 will use extra space for no reason.
  • If I understand you correctly, option 3 is similar to 1, except the extra union will not be visible in the API. json_value_internal * will be cast to json_value * and back. Best option indeed.

@LB-- LB-- added this to the v1.1.1 milestone Aug 14, 2021
Add LDFLAGS support and missing lm option
@ChromaticIsobar
Copy link
Contributor Author

@DimitriPapadopoulos I'll be away from work for a week, so I won't be able to answer before the 6th of September

@LB--
Copy link
Member

LB-- commented Sep 3, 2021

How to reproduce the compiler warning?

I do not see this warning on a 64-bit Ubuntu 18.04 machine, even with -pendatic or -Wall:
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

Do I need a 32-bit machine?

The warning only occurs when compiling for 64-bit architectures (e.g. x86_64 or ARM64) when unsigned long is 32-bits instead of 64-bits. So, typically you would encounter this warning when targeting Windows. For example, MSVC has C4311 enabled by default: https://gcc.godbolt.org/z/8Yo4Pjc3f

warning C4311: 'type cast': pointer truncation from 'void *' to 'unsigned long'

To get a similar warning for clang or gcc you would have to target Windows 64-bit as that is what causes unsigned long to be 32-bits while pointers are 64-bits, and you may have to explicitly enable -Wshorten-64-to-32.

@LB-- LB-- self-assigned this Sep 3, 2021
@ChromaticIsobar
Copy link
Contributor Author

I confirm what @LB-- said: this warning occurs when targeting 64-bit Windows.
I can reproduce the warning on my Ubuntu 16.04 machine using the x86_64-w64-mingw32-gcc compiler (version 5.3.1)

@DimitriPapadopoulos
Copy link
Contributor

Thank you @ChromaticIsobar. I won't have time to cross-compile to test that in the near future, could you perhaps check whether #133 helps in your case?

@ChromaticIsobar
Copy link
Contributor Author

ChromaticIsobar commented Sep 9, 2021

It does. It took a little because #133 caused stdlib.h not to be included and I had to add it in all sources that needed it.
But now it does compile!

@DimitriPapadopoulos
Copy link
Contributor

Do I get it right that <stdlib.h> is required by your own sources, not by json-parser itself?

@DimitriPapadopoulos
Copy link
Contributor

And actually this change has not removed <stdlib.h>, has it?

@ChromaticIsobar
Copy link
Contributor Author

Do I get it right that <stdlib.h> is required by your own sources, not by json-parser itself?

That's correct

Before #133 I included json.h, which in turn included stdlib.h
Now, json.h doesn't include stdlib.h anymore (but it includes stddef.h)

https://github.com/DimitriPapadopoulos/json-parser/blame/5a1fc1442a01ac5f4471931a59b8bee799f30c32/json.h#L47

So, I had to add the #include in my sources

@DimitriPapadopoulos
Copy link
Contributor

DimitriPapadopoulos commented Sep 9, 2021

Right, while you should always include <stdlib.h> yourself if you use definitions from that header, that's sort of a regression. Yet I am in favour of that regression, because the typical use is programmers including json.c and json.h directly in their projects, so that's something they should be able to handle, like you.

@ChromaticIsobar
Copy link
Contributor Author

I totally agree

@ChromaticIsobar
Copy link
Contributor Author

Do you want me to close this PR in favour of #133 ?

File test.py is executable in Git, yet does not start with "#!".
Forgot to merge this with the others...
@LB--
Copy link
Member

LB-- commented Oct 30, 2021

My plan currently is this:

The goal is to at least silence the warning for most people ASAP before engaging in a more drastic change for the sake of silencing the warning in C89. Any objections to this plan?

That's the standard over all the C standard library.
Either it has been defined explictly, or we attempt to calculate it
using the method reverted by fcfa748.

See discussion in json-parser#84 and json-parser#102.
It looks like a bad idea to compare apply to a double operators ++ and --,
or to compare to integers.
@LB--
Copy link
Member

LB-- commented Nov 3, 2021

Oops. Well, the PR is a bit of a mess now, but the relevant changes can be seen in b10ff61 - I guess now it just handles the edge case where size_t and uintptr_t aren't the same size...

@LB-- LB-- merged commit b10ff61 into json-parser:master Nov 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet