Skip to content

fix: Alter table issue#23

Merged
abmusse merged 6 commits intozendtech:masterfrom
abmusse:fix-alter-table-bug
Oct 4, 2022
Merged

fix: Alter table issue#23
abmusse merged 6 commits intozendtech:masterfrom
abmusse:fix-alter-table-bug

Conversation

@abmusse
Copy link
Copy Markdown
Contributor

@abmusse abmusse commented Aug 12, 2022

Fixes #22

Turns out that custom min and max macros here were buggy.

db2iengine/db2i_global.h

Lines 77 to 78 in 7339d24

#define min(a,b) ((long long)(a)>(long long)(b) ? (b) : (a))
#define max(a,b) ((long long)(a)<(long long)(b) ? (b) : (a))

In this situation min it returned a very large value that got passed into memcpy as num_bytes causing the failure.

memcpy(temp, part1, min(outlen, part2 - part1));

In this PR we remove using the min and max macros and instead use std::min and std::max.
This also required casting some variables to the appropriate types to work with std::min and std::max.

CC @kadler

@abmusse abmusse requested a review from ThePrez August 12, 2022 15:58
Comment thread db2i_file.cc Outdated
Comment on lines +380 to +381
memcpy(temp, part1, std::min(outlen, static_cast<size_t>(part2 - part1)));
temp[std::min(outlen-1, static_cast<size_t>(part2-part1))] = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like these should use num_bytes, no?

Copy link
Copy Markdown
Contributor Author

@abmusse abmusse Aug 15, 2022

Choose a reason for hiding this comment

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

yes. Thanks for catching that

Comment thread db2i_file.cc Outdated
Comment on lines +390 to +392
memset(temp, 0, std::min(outlen, static_cast<size_t>(part2-part1)));
memcpy(temp, part3, std::min(outlen, static_cast<size_t>(part4-part3)));
temp[std::min(static_cast<size_t>(outlen-1), static_cast<size_t>(part4-part3))] = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think a temp variable would be useful here. Also, looks like maybe the original code had a bug since the memset references part1 and part2, but the other two lines use part3 and part4.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Created temp varialble of num_bytes2 to store static_cast<size_t>(part4-part3)
Yes looks like bug as they memset with num_bytes of part2-part1 and later memcpy with num_bytes of part4-part3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not 100% certain it is a bug, since why would you need to memset if you're going to memcpy over the same amount of data? Of course, it could just be overly defensive code.

Comment thread db2i_misc.cc Outdated
if (delimit)
output[o++] = '"';
output[min(o, outlen-1)] = 0; // This isn't the most user-friendly way to handle overflows,
output[std::min( static_cast<size_t>(o), outlen-1)] = 0; // This isn't the most user-friendly way to handle overflows,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would probably change uint o to size_t o instead of the static_cast.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread ha_ibmdb2i.cc Outdated
forceSingleRowRead = true;
rowsToRead = 1;
}
rowsToRead = std::min(stats.records+1,std::min(rowsToRead, static_cast<ha_rows>(DEFAULT_MAX_ROWS_TO_BUFFER)));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

With the change of DEFAULT_MAX_ROWS_TO_BUFFER to an ha_rows, this static_cast should be unnecessary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread ha_ibmdb2i.cc
Comment on lines +2918 to +2919
convFromEbcdic(lastDupKeyNamePtr, unknownIndex, std::min(lastDupKeyNameLen, static_cast<uint32>(MAX_DB2_FILENAME_LENGTH)));
unknownIndex[std::min(lastDupKeyNameLen, static_cast<uint32>(MAX_DB2_FILENAME_LENGTH))] = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MAX_DB2_FILENAME_LENGTH should probably be a size_t and likely lastDupKeyNameLen too, but this seems fine for now.

@abmusse abmusse requested a review from kadler August 16, 2022 16:05
Comment thread db2i_file.cc Outdated
Comment on lines +379 to +381
size_t num_bytes = std::min(outlen, static_cast<size_t>(part2 - part1));
memcpy(temp, part1, std::min(outlen, num_bytes));
temp[std::min(outlen-1, num_bytes)] = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
size_t num_bytes = std::min(outlen, static_cast<size_t>(part2 - part1));
memcpy(temp, part1, std::min(outlen, num_bytes));
temp[std::min(outlen-1, num_bytes)] = 0;
size_t num_bytes = std::min(outlen-1, static_cast<size_t>(part2 - part1));
memcpy(temp, part1, num_bytes);
temp[num_bytes] = 0;

In the memcpy line, outlen has already been used to initialize num_bytes, no need to min against it again. In fact, once the min has been set there's no need to do any further calculations. The only change needed is to reserve one byte from the buffer length for the null terminator.

Comment thread ha_ibmdb2i.cc Outdated
ha_rows i = 1;
if (indexReadSizeEstimates)
return max(indexReadSizeEstimates[index], 1);
return std::max(indexReadSizeEstimates[index], i);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
return std::max(indexReadSizeEstimates[index], i);
return std::max(indexReadSizeEstimates[index], ha_rows{1});

Don't need the extraneous variable definition, without having the verbose static_cast syntax

Comment thread db2i_file.cc Outdated
memset(temp, 0, min(outlen, part2-part1));
memcpy(temp, part3, min(outlen, part4-part3));
temp[min(outlen-1, part4-part3)] = 0;
memset(temp, 0, std::min(outlen, num_bytes));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
memset(temp, 0, std::min(outlen, num_bytes));

This isn't needed since we're going to overwrite it anyway.

Comment thread db2i_file.cc Outdated
Comment on lines +391 to +393
size_t num_bytes2 = std::min(outlen, static_cast<size_t>(part4-part3));
memcpy(temp, part3, std::min(outlen, num_bytes2));
temp[std::min(static_cast<size_t>(outlen-1), num_bytes2)] = 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same as above. Also, we never use num_bytes from above again, so we could re-use it here instead of making num_bytes2.

Might be worth wrapping this code in to a function / lambda which can be called. Essentially what's being done is similar to strlcpy, eg.

// like strclpy, but takes srclen parameter too
auto strlcpy2 = [](char* dst, size_t size, const char* src, size_t srclen) {
  size = std::min(size-1, srclen);
  memcpy(dst, src, size);
  dst[size] = 0;
  return srclen;
}

That way we don't need the num_bytes variable around at all and the callsite is simplified to:

strlcpy2(tmp, outlen, part3, part4-part3);

Of course, as I look further, the only reason we are doing this copy in the first place is to extract a substring from in that is null-terminated so we can pass that string to smartFilenameToTableName. If smartFilenameToTableName took a "source length" parameter (instead of just assuming null-termination), we wouldn't even need to make any copies at all and most of this code would go away.

Maybe something like:

size_t db2i_table::smartFilenameToTableName(const char *in, size_t inlen, char* out, size_t outlen) {
 // copy old code here and adjust, using inlen
}

size_t db2i_table::smartFilenameToTableName(const char *in, char* out, size_t outlen) {
  return smartFilenameToTableName(in, strlen(in), out, outlen);
}
...

accumLen += smartFilenameToTableName(part3, part4-part3, &out[accumLen], outlen-accumLen);

Of course, now I'm really off in to the weeds for this change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We talked about this yesterday, the simplest "fix" for this is to just wrap these in a new scope:

{
  size_t num_bytes = std::min(outlen, static_cast<size_t>(part2 - part1));
  memcpy(temp, part1, std::min(outlen, num_bytes));
  temp[std::min(outlen-1, num_bytes)] = 0;
}

This way num_bytes cannot be used outside the scope and we can re-use the num_bytes name for each without contents of the variable accidentally affecting something else.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated

@abmusse abmusse requested a review from kadler October 3, 2022 12:02
@abmusse abmusse merged commit 7f96f1a into zendtech:master Oct 4, 2022
@abmusse abmusse deleted the fix-alter-table-bug branch October 4, 2022 15:53
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.

ALTER TABLE crashes the server

2 participants