Skip to content

Modified rule 14.1#1267

Merged
danmar merged 6 commits intocppcheck-opensource:masterfrom
swasti16:rule_14_1
Jun 4, 2018
Merged

Modified rule 14.1#1267
danmar merged 6 commits intocppcheck-opensource:masterfrom
swasti16:rule_14_1

Conversation

@swasti16
Copy link
Copy Markdown
Contributor

I have tried implementing rule 14.1 for while loop also.

Copy link
Copy Markdown
Collaborator

@danmar danmar left a comment

Choose a reason for hiding this comment

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

I think it could be more robust.

Comment thread addons/misra.py Outdated
lpar.astOperand2.astOperand2.astOperand2]

def getWhileLoopExpressions(whileToken):
if not whileToken or whileToken.str != 'while':
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I suggest :

if not simpleMatch(whileToken, 'while ('):
    return None

Comment thread addons/misra.py Outdated
return None
counter = lpar.astOperand2.astOperand1.str
token = lpar
while (token and token.str != "{"):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

use the token.link:

rpar = lpar.link

and then this check might be appropriate

if not simpleMatch(rpar, ') {'):
    return None

Comment thread addons/misra.py Outdated
lpar = whileToken.next
if not lpar or lpar.str != '(':
return None
if not lpar.astOperand2.astOperand1:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think you should check what lpar.astOperand2 is. if the code is:

while (a<100 || b > 100)

then the counter will be <

Comment thread addons/misra.py Outdated
return lpar.astOperand2
elif token.str == counter and token.astParent and token.astParent.str in {'++', '--'}:
return lpar.astOperand2
elif not token_link and whileToken.previous.str == '}':
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The not token_link and is redundant. You can just write:

elif whileToken.previous.str == '}':

@swasti16
Copy link
Copy Markdown
Contributor Author

I have made all suggested changes.

Copy link
Copy Markdown
Collaborator

@danmar danmar left a comment

Choose a reason for hiding this comment

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

I think it can be more robust still.

Comment thread addons/misra.py Outdated
while (token and token != token_link.link):
rpar = lpar.link
counter_token = lpar.next
while (counter_token != rpar):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would not loop like this. Now if the condition is:

while (abc.a < 10)

... then as I read it, the counter will be "abc"

How do you think this should be handled:

while (x < y)

... Do you think that x is a counter? Do you think that y is a counter? I think that either both should be counters or none should be counters.

I would recursively look at the condition from the parent..

def findCounterTokens(cond):
    if cond.str in ['&&' , '||']:
        c = findCounterTokens(cond.astOperand1)
        c.extend(findCounterTokens(cond.astOperand2))
        return c
    ret = []
    if cond.isComparisonOp and cond.astOperand1.isName and cond.astOperand2.isNumber:
         ret.append(cond.astOperand1)
    elif cond.isComparisonOp and cond.astOperand2.isName and cond.astOperand1.isNumber:
         ret.append(cond.astOperand2)
    return ret

and it will be called like this:

counter_tokens = findCounterTokens(lpar.astOperand2)

Comment thread addons/misra.py Outdated
expr = hasFloatComparison(counter_str.astParent)
if expr:
return True
elif simpleMatch(whileToken.previous, '} while'):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would recommend that you check that it's a do-while statement also.

Either:

elif simpleMatch(whileToken.previous, '} while') and simpleMatch(whileToken.previous.link.previous, 'do {')

Or (untested code, you probably need to adjust it a little):

elif simpleMatch(whileToken.previous, '} while') and whileToken.previous.scope.type == 'DO':

Copy link
Copy Markdown
Collaborator

@danmar danmar left a comment

Choose a reason for hiding this comment

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

various comments

Comment thread addons/misra.py
lpar.astOperand2.astOperand2.astOperand2]


def findCounterTokens(cond):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

it seems to me that you can cleanup and refactor this findCounterTokens.

Comment thread addons/misra.py Outdated


def getFloatComparision(floatCompOperator):
while not floatCompOperator.isComparisonOp:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this loop looks unsafe , are you sure that there will always be a parent that is a comparison op? I do not feel sure.

Comment thread addons/misra.py Outdated
return None
if simpleMatch(rpar, ') {'):
token = rpar.next
while (token != rpar.next.link):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this loop is more or less copy pasted and written twice. please refactor it so there is only 1 loop that looks at the while body.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I still think this is true.

I envision something like:

whileBodyStart = None
if simpleMatch(rpar, ') {'):
    whileBodyStart = rpar.next
elif simpleMatch(whileToken.previous, '} while') and simpleMatch(whileToken.previous.link.previous, 'do {'):
    whileBodyStart = whileToken.previous.link
else:
    return False

... check the while loop body ...

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.

Wow!!
I never thought this way!
Thanks a lot :)

Comment thread addons/misra.py Outdated
return hasFloatComparison(expr.astOperand1) or hasFloatComparison(expr.astOperand2)
if expr.isArithmeticalOp:
if expr.astOperand1.isFloat or expr.astOperand2.isFloat:
return expr.astOperand1.isFloat or expr.astOperand2.isFloat
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this expression is copy pasted so it can be changed to return True

Comment thread addons/test/misra-test.c Outdated

void misra_14_1() {
for (float f=0.1f; f<1.0f; f += 0.1f){} // 14.1
f = 0.0f;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this f seems to be out of scope. maybe f should be a local variable in this function instead so it can be used in the while loop conditions.

Comment thread addons/misra.py Outdated
return ret


def getFloatComparision(floatCompOperator):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Since this only returns True or False I think that some is... or has... name is better than get....

Comment thread addons/misra.py Outdated
ret.extend(findCounterTokens(cond.astOperand2))

if cond.isComparisonOp:
if cond.astOperand1.isName and cond.astOperand2.isNumber:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this logic seems to be copy pasted from isArithmeticalOp, it can be written only once.

Comment thread addons/misra.py Outdated
if cond.isArithmeticalOp:
if cond.astOperand1.isName and cond.astOperand2.isNumber:
ret.append(cond.astOperand1)
elif cond.astOperand2.isName and cond.astOperand1.isNumber:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do you need all these conditions that looks if cond.astOperand1 is a name or not? why not just:

if cond.astOperand1.isName:
    ret.append(cond.astOperand1)
if cond.astOperand2.isName:
    ret.append(cond.astOperand2)
....

Comment thread addons/misra.py Outdated
return False
if expr.isLogicalOp:
return hasFloatComparison(expr.astOperand1) or hasFloatComparison(expr.astOperand2)
if expr.isArithmeticalOp:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

it seems to me that with this code:

a = b + 5.0;

you will say that there is a float comparison. I do not agree we should return True for this code.

This function used to check if there was a comparison.. and if that was seen it was checked if one of the operands was a float type.

Copy link
Copy Markdown
Contributor Author

@swasti16 swasti16 May 30, 2018

Choose a reason for hiding this comment

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

I included this so that while((10.0f*c) > (a*b)) can return True.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

As far as I see the old code should return True for that. Maybe I need to look into this. Did the cppcheckdata.astIsFloat() return False for the expression 10.0f * c? Does it matter if c is declared?

Comment thread addons/test/misra-test.c
void misra_14_1() {
for (float f=0.1f; f<1.0f; f += 0.1f){} // 14.1
f = 0.0f;
while ((a<100.0f) || (b > 100)) //14.1
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

should it warn here if a is an int variable? I am just asking, I don't know.

I think that according to this example in the MISRA pdf, there should not be a warning for int loop counters:

float32_t f;
uint32_t u32a;
f = read_float32 ( );
do
{
    u32a = read_u32 ( );
} while ( ( ( float32_t ) u32a - f ) > 10.0f );

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.

Here 'a' is a float variable that's why a warning is raised.
If 'a' is an int variable there should not be any warning.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I do not see the declaration for a.

Copy link
Copy Markdown
Collaborator

@danmar danmar left a comment

Choose a reason for hiding this comment

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

minor comments

Comment thread addons/misra.py Outdated

def isFloatCounterInWhileLoop(whileToken):
if not simpleMatch(whileToken, 'while ('):
return None
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I feel that isFloatCounterInWhileLoop should return False/True instead of None/True.

Comment thread addons/misra.py Outdated
if not simpleMatch(whileToken, 'while ('):
return None
lpar = whileToken.next
if not lpar.astOperand2.astOperand1:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

hmm.. this if not lpar.astOperand2.astOperand1 condition looks redundant to me. Is it to avoid warnings for while (f)?

Comment thread addons/misra.py Outdated
exprs = getForLoopExpressions(token)
if exprs and hasFloatComparison(exprs[1]):
reportError(token, 14, 1)
if token.str != 'for' and token.str != 'while':
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You do not need to have this code:

if token.str != 'for' and token.str != 'while':
    continue

You can safely remove these 2 lines of code.

Comment thread addons/misra.py Outdated
for counter in findCounterTokens(exprs[1]):
if counter.valueType and counter.valueType.isFloat():
reportError(token, 14, 1)
if token.str == 'while':
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: use elif

Comment thread addons/misra.py
continue
if token.str == 'for':
exprs = getForLoopExpressions(token)
for counter in findCounterTokens(exprs[1]):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It would be a good idea to check that exprs is not None, wouldn't it? also is it best to check that len(exprs) is 3 or is that implied when exprs is not None?

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.

It would be a good idea to check that exprs is not None, wouldn't it?

Yes You are right.

also is it best to check that len(exprs) is 3 or is that implied when exprs is not None?

Yes .. according to me.. if exprs is not None.. len(exprs) is 3

Comment thread addons/misra.py Outdated
if not lpar.astOperand2.astOperand1:
return None
rpar = lpar.link
counter = findCounterTokens(lpar.astOperand2)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think you should rename counter to for instance counterTokens. Please make it obvious that it's a list.

Comment thread addons/misra.py Outdated
return None
rpar = lpar.link
counter = findCounterTokens(lpar.astOperand2)
if len(counter) == 0:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It seems safe to remove this code:

if len(counter) == 0:
    return None

Comment thread addons/misra.py Outdated
return None
if simpleMatch(rpar, ') {'):
token = rpar.next
while (token != rpar.next.link):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I still think this is true.

I envision something like:

whileBodyStart = None
if simpleMatch(rpar, ') {'):
    whileBodyStart = rpar.next
elif simpleMatch(whileToken.previous, '} while') and simpleMatch(whileToken.previous.link.previous, 'do {'):
    whileBodyStart = whileToken.previous.link
else:
    return False

... check the while loop body ...

Copy link
Copy Markdown
Collaborator

@danmar danmar left a comment

Choose a reason for hiding this comment

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

I like this patch now but I have a few minor nits.

Comment thread addons/misra.py Outdated
token = whileBodyStart
while (token != whileBodyStart.link):
token = token.next
for counter_str in counterTokens:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the name counter_str is confusing to me. Since it's not some string. how about counter or counterToken.

Comment thread addons/misra.py Outdated
token = token.next
for counter_str in counterTokens:
if token.isAssignmentOp and token.astOperand1.str == counter_str.str:
if counter_str.valueType and counter_str.valueType.isFloat():
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

hmm.. this condition does not have to be duplicated:

for counter_str in counterTokens:
    if not counter_str.valueType or not counter_str.valueType.isFloat():
        continue
    ....

Comment thread addons/misra.py Outdated
if counter.valueType and counter.valueType.isFloat():
reportError(token, 14, 1)
elif token.str == 'while':
exprs = isFloatCounterInWhileLoop(token)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the name exprs is confusing to me. I would not use a variable I would just write:

if isFloatCounterInWhileLoop(token):

@danmar
Copy link
Copy Markdown
Collaborator

danmar commented Jun 4, 2018

I see nothing more to complain about :-) Thanks!

@danmar danmar merged commit bdb372f into cppcheck-opensource:master Jun 4, 2018
@swasti16 swasti16 deleted the rule_14_1 branch June 14, 2018 04:43
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