8000 [Safety] Remove C Style Casts by Mytherin · Pull Request #6967 · duckdb/duckdb · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[Safety] Remove C Style Casts #6967

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

Merged
merged 31 commits into from
Apr 5, 2023
Merged

Conversation

Mytherin
Copy link
Collaborator
@Mytherin Mytherin commented Apr 4, 2023

This PR removes most usages of C-style casts when casting to derived classes, such as when casting from e.g. a ParsedExpression to a FunctionExpression, or a LogicalOperator to a LogicalGet, etc. Instead, we replace these casts with a templated Cast function that returns a reference to the derived member. For example, this:

void MyFunction(const ParsedExpression &expr) {
	auto &function = (FunctionExpression &) expr;
	...
}

Now becomes this:

void MyFunction(const ParsedExpression &expr) {
	auto &function = expr.Cast<FunctionExpression>();
	...
}

This has a number of advantages over C-style casts:

  • The Cast function will check that the cast is to the correct type (e.g. for expressions by looking at the expression_class variable) - an InternalException is thrown when a cast to an incorrect type is performed
  • The Cast function cannot remove const-ness of values by accident. If expr is a const, then the returned value is also const.
  • The Cast function will only compile if called correctly, as opposed to C-style casts. For example, this will compile (and crash):
void MyFunction(const ParsedExpression *expr) {
	auto &function = (FunctionExpression &) expr; // EEK: casting a pointer to a reference
	...
}

But this will result in a compilation error:

void MyFunction(const ParsedExpression *expr) {
	auto &function = expr.Cast<FunctionExpression>(); // compile error: need to use -> instead
	...
}

@Tishj
Copy link
Contributor
Tishj commented Apr 4, 2023

I had a hard time finding an implementation of this in the changes, so for anyone else looking:

public:
	template <class TARGET>
	TARGET &Cast() {
		D_ASSERT(dynamic_cast<TARGET *>(this));
		return (TARGET &)*this;
	}

	template <class TARGET>
	const TARGET &Cast() const {
		D_ASSERT(dynamic_cast<const TARGET *>(this));
		return (const TARGET &)*this;
	}

I wonder if this can't be done in a generic fashion, instead of requiring to be implemented on the type itself

Cast<FunctionExpression>(expr);

Using some template magic, we should be able to replicate the const/non-const separation

I think it should also be able to check for the existence of a TYPE variable with enable_if, and overload on that
to support this variation:

	TARGET &Cast() {
		if (expression_class != TARGET::TYPE) {
			throw InternalException("Failed to cast expression to type - expression type mismatch");
		}
		return (TARGET &)*this;
	}

I think Max did something similar recently for the serializer/deserializer

But we might prefer to have this as an explicit function on the type, so we can easily alter it to the type and have the error be as descriptive as possible, instead of wading through layers of templating to make a meaningful change

8000

@Mytherin
Copy link
Collaborator Author
Mytherin commented Apr 4, 2023

I thought about trying that but opted not to for various reasons - most Cast functions are slightly different, and they are small enough that I don't feel bad copying them around a few times.

@carlopi
Copy link
Contributor
carlopi commented Apr 4, 2023

Nice!
Potentially some checks could be moved at compile time (so that IFF starting type is known, it will fail to compile right away, but I am not sure it's feasible).

In any case if type is deducible ahead of time, it will be already properly code-generated (with the checking elided and either the call or the throw known), so that is a big nice gain.

@Tishj
Copy link
Contributor
Tishj commented Apr 5, 2023

The thread sanitizer error is caused by the change to the concurrentqueue.hpp header guard, probably need to add to .sanitizer-thread-suppressions.txt I didn't know what exactly needs to be added.

concurrentqueue.h does already have a header guard, so the only thing this is now protecting against double include is the single threaded case - in which case hitting the thread sanitizer makes no sense?

@@ -382,7 +382,7 @@ unique_ptr<BenchmarkState> InterpretedBenchmark::Initialize(BenchmarkConfigurati
state->con.Query("PRAGMA enable_profiling");
state->con.Query("PRAGMA profiling_mode='detailed'");
}
return state;
return std::move(state);
Copy link
Contributor
@Tishj Tishj Apr 5, 2023

Choose a reason for hiding this comment

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

Looks like you ran into #6964, I can't seem to reproduce this.
There are quite a lot of places we rely on this behavior, no?

Or I guess the issue lies in not being able to implicitly convert a unique_ptr of a derived class to the base class, but when we move it, the move constructor kicks in and that does allow the conversion

Copy link
Collaborator Author
@Mytherin Mytherin Apr 5, 2023

Choose a reason for hiding this comment

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

No, GCC-4.9 does not support it for the regular std::unique_ptr so we already had std::move all over the place for that compiler. I ran into it on a Linux box while compiling the benchmark module

@M
8000
ytherin Mytherin merged commit 8edeaf4 into duckdb:master Apr 5, 2023
@Mytherin Mytherin mentioned this pull request Apr 5, 2023
2 tasks
@Mytherin Mytherin deleted the removecstylecasts branch April 24, 2023 13:35
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.

3 participants
0