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 undefined behavior #210

Closed
wants to merge 2 commits into from
Closed

Fix undefined behavior #210

wants to merge 2 commits into from

Conversation

ctiller
Copy link
Contributor

@ctiller ctiller commented Feb 13, 2015

It's undefined behavior to dereference a NULL pointer, even if the value pointed to doesn't get used.

It's undefined behavior to dereference a NULL pointer, even if the value pointed to doesn't get used.
@ctiller
Copy link
Contributor Author

ctiller commented Feb 13, 2015

This problem was detected running with clang with -fsanitize=undefined.

limit = &old_rep->elements[current_size_];
for (; e < limit; e++) {
e->Element::~Element();
if (current_size_ > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe simpler just to write:

for (size_t i = 0; i < current_size_; i++) {
Element* e = old_rep->elements[i];
e->Element::~Element();
}

Also doesn't some of the code above have the same problem if new_size == 0 (that doesn't make a lot of sense to do, but it shouldn't trigger undefined behavior).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right: will update.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Careful! If Element::~Element() is not visible to the compiler, it implies a memory barrier, so the old_rep->elements fetch is not loop-invariant, so it can't be hoisted out of the loop. That's the reason for the e and limit pointers pre-computed above the loop. This code is very sensitive for both code size and performance, so please verify equivalent machine code for types with nontrivial destructors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(And to be clear, when I say "memory barrier" I mean "barrier across which loads/stores can't be moved", not a true hardware barrier.)

@haberman
Copy link
Member

Unfortunately, for the reasons Chris mentioned the more complicated code is probably required for efficiency/code-size reasons. :(

@cfallin
Copy link
Contributor

cfallin commented Mar 25, 2015

Revisiting with new comment -- I think the if surrounding the loop is probably fine, depending on code-size impact. It's the repeated load of elements per loop iteration that would cause more trouble. @ctiller, feel free to ping me about tests to run (code size and performance) if you want to pursue this.

@haberman
Copy link
Member

Hi @ctiller, I'm cleaning out old PRs. It looks like the second part of your PR has been fixed in the meantime -- the second loop is now surrounded with if (old_rep) {}. And the first part doesn't seem necessary -- rep_ was just allocated, so should never be NULL.

Have you seen any more of these sanitize errors since filing this report? Do you have an easy way of re-running this test?

@acozzette acozzette closed this Jun 8, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants