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

kernel: avoid using result of an assignment operator #72226

Conversation

DeHess
Copy link
Collaborator

@DeHess DeHess commented May 2, 2024

Fix coding guideline MISRA C:2012 Rule 13.4 in kernel:

The result of an assignment operator should not be used.

This PR is part of the enhancement issue #48002 which port the coding guideline fixes done by BUGSENG on the https://github.com/zephyrproject-rtos/zephyr/tree/v2.7-auditable-branch back to main

The commit in this PR is a subset of the original auditable-branch commit:
64336f4

@andyross
Copy link
Contributor

andyross commented May 2, 2024

How sticky is this particular rule? The last time the MISRApolcalypse came by, I remember arguing strongly that this particular pattern ("while((iterator = next()) != NULL)") was cleaner, safer and generally more readable than the alternatives, and (I think?) winning the argument.

In particular what you're doing is replacing them with a conceptually infinite loop (!!), which seems like the pessimal choice. Ew.

Can you at least do this with an equivalent for() construction instead? e.g. something along the lines of for(whatever* iter = next(); iter != NULL; iter = next())

Copy link
Member

@cfriedt cfriedt left a comment

Choose a reason for hiding this comment

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

Would probably be better to use do .. while loops in most of these situations.

Also, for (;;) can be on the fence in terms of coding style (although I personally like using it).

kernel/condvar.c Outdated
Comment on lines 87 to 91
for (;;) {
pending_thread = z_unpend_first_thread(&condvar->wait_q);
if (pending_thread == NULL) {
break;
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
for (;;) {
pending_thread = z_unpend_first_thread(&condvar->wait_q);
if (pending_thread == NULL) {
break;
}
do {
pending_thread = z_unpend_first_thread(&condvar->wait_q);
} while(pending_thread != NULL);

kernel/msg_q.c Outdated
Comment on lines 386 to 390
for (;;) {
pending_thread = z_unpend_first_thread(&msgq->wait_q);
if (pending_thread == NULL) {
break;
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
for (;;) {
pending_thread = z_unpend_first_thread(&msgq->wait_q);
if (pending_thread == NULL) {
break;
}
do {
pending_thread = z_unpend_first_thread(&msgq->wait_q);
} while (pending_thread != NULL);

kernel/sched.c Outdated Show resolved Hide resolved
kernel/sched.c Outdated
Comment on lines 1264 to 1268
for (;;) {
thread = z_waitq_head(wait_q);
if (thread == NULL) {
break;
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
for (;;) {
thread = z_waitq_head(wait_q);
if (thread == NULL) {
break;
}
do {
thread = z_waitq_head(wait_q);
} while (thread != NULL);

@andyross
Copy link
Contributor

andyross commented May 3, 2024

Agreed that do/while is cleaner than for for a lot of these, but I have a nagging memory that MISRA disallows those too? Or maybe I'm confusing it with some other coding standard. But regardless let's try to do this with some care towards coding aesthetics. Unbounded loops where you have to dig to understand the invariant constraints are just bad.

@DeHess
Copy link
Collaborator Author

DeHess commented May 7, 2024

Would probably be better to use do .. while loops in most of these situations.

Also, for (;;) can be on the fence in terms of coding style (although I personally like using it).

Unfortunately these proposed changes only work if the condition/assignment is the only thing happening in the loop. Sadly that is not the case here. Even with do-while we'd be forced to have an if statement in there to break in case pending_thread == NULL. I don't think we can avoid such an if statement if we want to adhere to this Misra Guideline

Now whether we use for(;;), while, or do-while thats a different topic altogether.

@DeHess
Copy link
Collaborator Author

DeHess commented May 7, 2024

How sticky is this particular rule? The last time the MISRApolcalypse came by, I remember arguing strongly that this particular pattern ("while((iterator = next()) != NULL)") was cleaner, safer and generally more readable than the alternatives, and (I think?) winning the argument.

In particular what you're doing is replacing them with a conceptually infinite loop (!!), which seems like the pessimal choice. Ew.

Can you at least do this with an equivalent for() construction instead? e.g. something along the lines of for(whatever* iter = next(); iter != NULL; iter = next())

From what I understand changing it to a for loop would have to look like this to not violate the rule:

pending_thread = z_unpend_first_thread(&condvar->wait_q);
for (pending_thread; pending_thread != NULL; pending_thread = z_unpend_first_thread(&condvar->wait_q)) {
...
}

Else we're still utilising the result of an assignment operator directly. At least I think so.
Would you prefer this over the for(;;)?

@DeHess DeHess requested a review from cfriedt May 7, 2024 09:23
@cfriedt
Copy link
Member

cfriedt commented May 7, 2024

Would probably be better to use do .. while loops in most of these situations.

Also, for (;;) can be on the fence in terms of coding style (although I personally like using it).

Unfortunately these proposed changes only work if the condition/assignment is the only thing happening in the loop. Sadly that is not the case here.

In the areas where I suggested changes, jte assignment was the only thing happening in the loop.

Even with do-while we'd be forced to have an if statement in there to break in case pending_thread == NULL.

Wait... on what planet would the loop not exit with the condition while (pending_thread != NULL)?

I can understand if there were other instructions within the loop other than a comparison, but there are none.

I don't think we can avoid such an if statement if we want to adhere to this Misra Guideline

What is / are the exact MISRA rules that this change purportedly adheres to?

Rule 6-6-4 states "For any iteration statement there shall be no more than one break or goto statement used for loop termination."

Using the loop condition to terminate the loop is definitely allowed. do .. while loops are definitely allowed.

Is there maybe a misinterpretation that there should be exactly one break statement rather than maximally one break statement?

Ref:
https://www.synopsys.com/software-integrity/static-analysis-tools-sast/misra.html

Now whether we use for(;;), while, or do-while thats a different topic altogether.

for(;;) is an infinite loop, implicitly, as Andy mentioned, which is (AFAIK) also not allowed.

Copy link
Contributor

@andyross andyross left a comment

Choose a reason for hiding this comment

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

Else we're still utilising the result of an assignment operator directly. At least I think so.

Uh... no? Can you explain where you're seeing that. This was my suggestion:

for(whatever_t *iter = next(); iter != NULL; iter = next()) { ... }

The first of the three for() expressions is an initializer. It declares a local variable and initializes it. Strictly that's not an "assignment operator" at all, but even if it were the result of it isn't being used outside the statement. The second is a test, no assignment operator. The third is an assignment operator which is used only for its side effects, no further use of the result of the "=" operator is made.

But the broader point is this: loops whose invariants are expressed as internal exit conditions and not using the proper C syntax for loop invariants are bad style. And frankly they're much worse style than anything MISRA is trying to prevent. I'm neutral on do/while, for(), etc... There are a million ways to write perfectly readable code. But an infinite loop with a break inside of it is not one. There are circumstances where you need to write such a thing, but they're rare (and generally isomorphic to something like "error handling").

I think I'm -1 on this. If the compliance rules and the requirements for them are really inflexible, then escalate and let's have the fight to overrule me.

@DeHess
Copy link
Collaborator Author

DeHess commented May 8, 2024

@cfriedt
Currently, the first subroutine in upstream main looks like this:
(condvar.c, starting on line 87)

while ((pending_thread = z_unpend_first_thread(&condvar->wait_q)) != NULL) {
	woken++;
	arch_thread_return_value_set(pending_thread, 0);
	z_ready_thread(pending_thread);
}

BUGSENG authored and I committed the following change initially:

for (;;) {
	pending_thread = z_unpend_first_thread(&condvar->wait_q);
	if (pending_thread == NULL) {
		break;
	} 
	woken++;
	arch_thread_return_value_set(pending_thread, 0);
	z_ready_thread(pending_thread);
}

You requested these changes:

do {
	pending_thread = z_unpend_first_thread(&condvar->wait_q);
} while(pending_thread != NULL);
	woken++;
	arch_thread_return_value_set(pending_thread, 0);
	z_ready_thread(pending_thread);
}	

The initial subroutine is not only an assignment and a condition. You are right on all other counts however.
I will use a for loop just like @andyross proposed. Personally I think that would be the most satisfactory solution

@DeHess DeHess force-pushed the misra_rule_13_4_auditable_to_main_kernel branch 2 times, most recently from cb1835d to f1b6174 Compare May 8, 2024 08:04
avoid the direct use of assignment expression
values for conditions

Signed-off-by: Hess Nathan <nhess@baumer.com>
@DeHess DeHess force-pushed the misra_rule_13_4_auditable_to_main_kernel branch from f1b6174 to 31173c6 Compare May 8, 2024 10:42
@DeHess DeHess requested a review from andyross May 8, 2024 12:03
Copy link
Contributor

@andyross andyross left a comment

Choose a reason for hiding this comment

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

OK, thanks. I don't hate this version. :)

@DeHess
Copy link
Collaborator Author

DeHess commented May 8, 2024

OK, thanks. I don't hate this version. :)

Thanks for bearing with me for this one :)

@nashif nashif added the area: Coding Guidelines Coding guidelines and style label May 27, 2024
@aescolar aescolar merged commit 20b5542 into zephyrproject-rtos:main May 28, 2024
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Coding Guidelines Coding guidelines and style area: Kernel
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants