use base ("Understand::Codecheck"); use strict; use constant ERR1 => "Switch Incorrect: Case detected after default"; use constant ERR2 => "Switch Incorrect: No default case"; sub register_tr_text { my $check = shift; $check->add_tr_text(ERR1); $check->add_tr_text(ERR2); } sub name { return "15.3 The final clause of a switch statement shall be the default clause"; } sub description { return "15.3 (Required) The final clause of a switch statement shall be the default clause."; } sub detailed_description { return <<"END_DESC" The requirement for a final default clause is defensive programming. This clause shall either take appropriate action or contain a suitable comment as to why no action is taken. END_DESC } sub test_language { my $language = shift; return $language =~ /C\+\+/; } sub test_entity { return 1; } sub test_global { return 0; } sub check { my $check = shift; my $file = shift; return unless $file->kind->check("file ~unresolved ~unknown"); # create lexer once for file my ($lexer,$status) = $file->lexer(); die "Cannot open a lexer to ".$file->relname().": $status\n" if $status; # loop through functions defined in the file foreach my $ref ($file->filerefs("define","function",1)) { my $func = $ref->ent(); my ($begin,$end) = getFunctionDefnLines($func); next if (!$begin); do_one_function($check,$file,$func,$lexer,$begin,$end); } } # Check one function. sub do_one_function { my ($check,$file,$func,$lexer,$begin,$end) = @_; setupLexemes($lexer,$begin,$end); while (my $lexeme = nextLexeme()) { handle_switch($check,$file,$func) if $lexeme->text() eq "switch"; } } # Handle a switch statement. Begin with current lexeme at the 'switch' # token and end with current lexeme at the closing '}' of the switch. sub handle_switch { my ($check,$file,$func) = @_; # skip to first curly brace my $lexeme = nextLexeme() or return; while ($lexeme->text() ne "{") { $lexeme = nextLexeme() or return; } # process everything until closing brace my $seen_default=0; my $choice_okay=1; while ($lexeme = nextLexeme(1)) { # allow comment lexeme my $text = $lexeme->text(); last if $text eq "}"; if ($text eq "case") { if ($seen_default) { $check->violation($func,$file,$lexeme->line_end,$lexeme->column_end,ERR1); } $choice_okay = 0; $lexeme = nextLexeme() or return 0 while $lexeme->text() ne ":"; } elsif ($text eq "default") { $seen_default = 1; $choice_okay = 0; $lexeme = nextLexeme() or return 0 while $lexeme->text() ne ":"; } elsif ($lexeme->token() eq "Comment") { $choice_okay = 1; } else { $choice_okay = handle_statement($check,$file,$func); } } $lexeme = lastLexeme() if !$lexeme; if (!$seen_default) { $check->violation($func,$file,$lexeme->line_end,$lexeme->column_end,ERR2); } } # Handle a statement, within a switch context. # Begin with current lexeme (passed in) at the first token of the statement # and end with the current lexeme at the last token of the statement. # Return 1 if a new choice statement is okay after this statement (ie, this # is a break/return statement or an if/else series of statements where all # paths end in break/return). sub handle_statement { my ($check,$file,$func) = @_; my $lexeme = currentLexeme() or return 0; my $text = $lexeme->text(); # handle 'switch' statement, always returns 0. if ($text eq "switch") { handle_switch($check,$file,$func); return 0; } # handle 'for' or 'while' statement, always returns 0. if ($text eq "for" || $text eq "while") { $lexeme = nextLexeme() or return 0 while $lexeme->text() ne "("; my $paren=1; while ($paren && ($lexeme = nextLexeme())) { $text = $lexeme->text(); if ($text eq "(") { ++$paren; } elsif ($text eq ")") { --$paren; } } nextLexeme(); handle_statement($check,$file,$func); return 0; } # handle 'do' statement, always returns 0. if ($text eq "do") { nextLexeme(); handle_statement($check,$file,$func); return 0; } # handle 'if' statement. if ($text eq "if") { $lexeme = nextLexeme() or return 0 while $lexeme->text() ne "("; my $paren=1; while ($paren && ($lexeme = nextLexeme())) { $text = $lexeme->text(); if ($text eq "(") { ++$paren; } elsif ($text eq ")") { --$paren; } } nextLexeme(); my $okay = handle_statement($check,$file,$func); $lexeme = peekNextLexeme() or return 0; return 0 if $lexeme->text() ne "else"; nextLexeme(); nextLexeme(); return handle_statement($check,$file,$func) && $okay; } # handle compound statement if ($text eq "{") { my $okay=0; while (($lexeme = nextLexeme()) && $lexeme->text() ne "}") { $okay = handle_statement($check,$file,$func); } return $okay; } my $okay = ($text eq "return" || $text eq "break"); while ($text ne ";") { $lexeme = nextLexeme() or return $okay; $text = $lexeme->text(); } return $okay; } # Pass a function entity. Return an array of: # the begin line # the end line # the defn file entity # Return undef if this info cannot be provided. sub getFunctionDefnLines { my $func = shift; my $begin_ref = $func->ref("definein"); my $end_ref = $func->ref("end"); return undef if (!$begin_ref || !$end_ref); return ($begin_ref->line(), $end_ref->line(), $begin_ref->file()); } # Setup the global lexemes array once per function, to use the NextLexeme() sub. my @lexemes=(); my $lexeme_pos=0; sub setupLexemes { my $lexer = shift; my $begin = shift; my $end = shift; @lexemes = $lexer->lexemes($begin,$end); $lexeme_pos = 0; } # Return the current lexeme. sub currentLexeme { return $lexemes[$lexeme_pos-1]; } # Return the last lexeme. sub lastLexeme { return $lexemes[$#lexemes-1] } # Peek and return the next lexeme, but do not update the current lexeme. sub peekNextLexeme { my $pos = $lexeme_pos; my $next = nextLexeme(); $lexeme_pos = $pos; return $next; } # Return the next interesting lexeme or undef when all lexemes are used. sub nextLexeme { my $comment_okay = shift; while ($lexeme_pos < $#lexemes) { my $lexeme = $lexemes[$lexeme_pos++]; my $token = $lexeme->token(); if ($token eq "Preprocessor") { while ($lexeme_pos < $#lexemes && $lexemes[$lexeme_pos]->token() ne "Newline") { ++$lexeme_pos; } next; } elsif ($comment_okay) { next if ($lexeme->token() =~ m/Whitespace|Newline/); } else { next if ($lexeme->token() =~ m/Comment|Whitespace|Newline/); } return $lexeme; } return undef; } 1;