blob: c2a2d1cd97356b52e8d37bba8c50144b442bfa38 [file] [log] [blame]
# **********************************************************
# Copyright (c) 2010 VMware, Inc. All rights reserved.
# **********************************************************
# Dr. Memory: the memory debugger
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License, and no later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# **********************************************************
# Copyright (c) 2009-2010 VMware, Inc. All rights reserved.
# **********************************************************
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of VMware, Inc. nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.
###########################################################################
# Code Review Automation
# Based on DynamoRIO's script of the same name
#
# See CodeReviews.wiki for full discussion of our code review process.
# To summarize: reviewer commits to /reviews/<user>/<year>/i###-descr.diff
# with a commit log that auto-opens an Issue
# Inline comments for a regular patch diff should be the norm for now
# FIXME: we should also produce web diffs for quick reviews
# FIXME: xref discussion on using Google Code's post-commit review options
#
# Usage: prepare a notes file with comments for the reviewer after a line
# beginning "toreview:", and point to it w/ the NOTES variable
# (defult is ./diff.notes)
#
# Since we no longer invoke "make" from the soure dir (due to cmake: i#19, i#64),
# we have to invoke this explicitly "cmake -P ./codereview.cmake".
#
# Parameter list:
#
# REVIEWS:PATH = Checkout of https://drmemory.googlecode.com/svn/reviews
# NOTES:FILEPATH = File containing notes for this review.
# It must contain the string label "toreview:".
# Content prior to the label is discarded (private notes).
# Content after the label is included in the review request.
# LABEL:STRING = Name for review. Should follow "i###-descr" format where
# ### is the Issue number. For example: "i64-cmake-review".
# AUTHOR:STRING = Username on https://drmemory.googlecode.com/svn
# REVIEWER:STRING = Username on https://drmemory.googlecode.com/svn
# REVERT:BOOL = Set to ON to revert locally-added files
# COMMIT:BOOL = Set to ON to officially commit the review request
# (the default is to simply create and locally add the
# .diff and .notes files)
#
# Examples:
#
# Dry run to ensure diff and notes file are as desired:
# > cmake -DAUTHOR:STRING=derek.bruening -DREVIEWER:STRING=qin.zhao -DLABEL:STRING=i64-cmake-review -P ./codereview.cmake
# -- notes file is "diff.notes"
# -- destination is "../reviews/derek.bruening/2009/i64-cmake-review.{diff,notes}"
# -- svn: A ../reviews/derek.bruening/2009/i64-cmake-review.diff
# -- svn: A ../reviews/derek.bruening/2009/i64-cmake-review.notes
# -- ready to commit
#
# Want to abort (maybe decided to change LABEL) so undoing local svn add:
# > cmake -DAUTHOR:STRING=derek.bruening -DREVIEWER:STRING=qin.zhao -DLABEL:STRING=i64-cmake-review -DREVERT:BOOL=ON -P ./codereview.cmake
# -- notes file is "diff.notes"
# -- destination is "../reviews/derek.bruening/2009/i64-cmake-review.{diff,notes}"
# -- svn: D ../reviews/derek.bruening/2009/i64-cmake-review.diff
# -- svn: D ../reviews/derek.bruening/2009/i64-cmake-review.notes
# -- revert complete
#
# Ready to commit:
# > cmake -DAUTHOR:STRING=derek.bruening -DREVIEWER:STRING=qin.zhao -DLABEL:STRING=i64-cmake-review -DCOMMIT:BOOL=ON -P ./codereview.cmake
# -- notes file is "diff.notes"
# -- destination is "../reviews/derek.bruening/2009/i64-cmake-review.{diff,notes}"
# -- svn: A ../reviews/derek.bruening/2009/i64-cmake-review.diff
# -- svn: A ../reviews/derek.bruening/2009/i64-cmake-review.notes
# -- svn: Sending derek.bruening/2009/i64-cmake-review.diff
# -- svn: Sending derek.bruening/2009/i64-cmake-review.notes
# -- svn: Transmitting file data .
# -- svn: Committed revision 75.
# -- committed
#
# Note that you can make a cmake script that sets common variables and point at it
# with the -C parameter to cmake.
# FIXME i#66: should include pre-commit regression test suite results here
if (NOT DEFINED LABEL)
message(FATAL_ERROR "must set LABEL variable")
endif (NOT DEFINED LABEL)
if (NOT DEFINED AUTHOR)
message(FATAL_ERROR "must set AUTHOR variable")
endif (NOT DEFINED AUTHOR)
if (NOT DEFINED REVIEWER)
message(FATAL_ERROR "must set REVIEWER variable")
endif (NOT DEFINED REVIEWER)
if (NOT DEFINED NOTES)
set(NOTES diff.notes)
endif (NOT DEFINED NOTES)
if (NOT EXISTS ${NOTES})
message(FATAL_ERROR "cannot find NOTES=\"${NOTES}\"")
endif (NOT EXISTS ${NOTES})
message(STATUS "notes file is \"${NOTES}\"")
if (NOT DEFINED REVIEWS)
set(REVIEWS ../reviews)
endif (NOT DEFINED REVIEWS)
if (NOT EXISTS ${REVIEWS})
message(FATAL_ERROR "cannot find REVIEWS=\"${REVIEWS}\"")
endif (NOT EXISTS ${REVIEWS})
find_program(SVN svn DOC "subversion client")
if (NOT SVN)
message(FATAL_ERROR "svn not found")
endif (NOT SVN)
# we run svn in the review checkout
function(run_svn)
execute_process(COMMAND ${SVN} ${ARGV}
WORKING_DIRECTORY ${REVIEWS}
RESULT_VARIABLE svn_result
ERROR_VARIABLE svn_err
OUTPUT_VARIABLE svn_out)
if (svn_result OR svn_err)
message(FATAL_ERROR "*** ${SVN} ${ARGV} failed: ***\n${svn_err}")
endif (svn_result OR svn_err)
string(STRIP "${svn_out}" svn_out)
string(REGEX REPLACE "\r?\n" "\n-- svn: " svn_out "${svn_out}")
message(STATUS "svn: ${svn_out}")
endfunction(run_svn)
find_program(DIFFSTAT diffstat DOC "diff statistics displayer")
if (NOT DIFFSTAT)
message(STATUS "diffstat not found: will not have diff statistics")
endif (NOT DIFFSTAT)
# get the year
if (UNIX)
find_program(DATE date DOC "unix date")
if (NOT DATE)
message(FATAL_ERROR "date not found")
endif (NOT DATE)
execute_process(COMMAND ${DATE} +%Y
RESULT_VARIABLE date_result
ERROR_VARIABLE date_err
OUTPUT_VARIABLE year)
if (date_result OR date_err)
message(FATAL_ERROR "*** ${DATE} failed: ***\n${date_err}")
endif (date_result OR date_err)
string(STRIP "${year}" year)
else (UNIX)
find_program(CMD cmd DOC "cmd shell")
if (NOT CMD)
message(FATAL_ERROR "cmd not found")
endif (NOT CMD)
# If use forward slashes => "The syntax of the command is incorrect."
file(TO_NATIVE_PATH "${CMD}" CMD)
execute_process(COMMAND ${CMD} /c date /T
RESULT_VARIABLE date_result
ERROR_VARIABLE date_err
OUTPUT_VARIABLE date_out)
if (date_result OR date_err)
message(FATAL_ERROR "*** ${CMD} /c date /T failed: ***\n${date_err}")
endif (date_result OR date_err)
string(STRIP "${date_out}" date_out)
# result should be like this: "Fri 03/06/2009"
# cmake regexp doesn't have {N}
string(REGEX REPLACE "^....../../([0-9][0-9][0-9][0-9])" "\\1" year "${date_out}")
endif (UNIX)
# we run from the REVIEWS dir so no need to include it here
set(DEST_BASE "${AUTHOR}/${year}")
set(DEST "${DEST_BASE}/${LABEL}")
message(STATUS "destination is \"${REVIEWS}/${DEST}.{diff,notes}\"")
if (REVERT)
execute_process(COMMAND ${SVN} status ${DEST}.diff
WORKING_DIRECTORY ${REVIEWS}
RESULT_VARIABLE svn_result
ERROR_VARIABLE svn_err
OUTPUT_VARIABLE svn_out)
if (svn_result OR svn_err)
message(FATAL_ERROR "*** ${SVN} status failed: ***\n${svn_err}")
endif (svn_result OR svn_err)
if ("${svn_out}" MATCHES "^\\A")
# svn revert doesn't delete local copy if new => svn delete
# FIXME: if AUTHOR was mistyped should we remove the directories too?
run_svn("delete;--force;${DEST}.diff;${DEST}.notes")
else ("${svn_out}" MATCHES "^\\A")
run_svn("revert;${DEST}.diff;${DEST}.notes")
endif ("${svn_out}" MATCHES "^\\A")
message(STATUS "revert complete")
else (REVERT)
if (NOT EXISTS "${REVIEWS}/${DEST_BASE}")
if (NOT EXISTS "${REVIEWS}/${AUTHOR}")
run_svn("mkdir;${AUTHOR}")
endif (NOT EXISTS "${REVIEWS}/${AUTHOR}")
run_svn("mkdir;${DEST_BASE}")
endif (NOT EXISTS "${REVIEWS}/${DEST_BASE}")
set(NOTES_LOCAL "${DEST}.notes")
set(DIFF_LOCAL "${DEST}.diff")
set(NOTES_FILE "${REVIEWS}/${NOTES_LOCAL}")
set(DIFF_FILE "${REVIEWS}/${DIFF_LOCAL}")
# If someone manually created these files then this script
# will fail: we assume not already there if haven't been
# added to svn yet.
if (NOT EXISTS "${DIFF_FILE}")
file(WRITE "${DIFF_FILE}" "")
run_svn("add;${DIFF_LOCAL}")
endif (NOT EXISTS "${DIFF_FILE}")
if (NOT EXISTS "${NOTES_FILE}")
file(WRITE "${NOTES_FILE}" "")
run_svn("add;${NOTES_LOCAL}")
endif (NOT EXISTS "${NOTES_FILE}")
# We want context diffs with procedure names for better readability
# svn diff does show new files for us but we pass -N just in case
execute_process(COMMAND ${SVN} diff --diff-cmd diff -x "-c -p -N"
RESULT_VARIABLE svn_result
ERROR_VARIABLE svn_err
OUTPUT_FILE "${DIFF_FILE}")
if (svn_result OR svn_err)
message(FATAL_ERROR "*** ${SVN} diff failed: ***\n${svn_err}")
endif (svn_result OR svn_err)
file(READ ${NOTES} string)
string(REGEX REPLACE "^.*\ntoreview:\r?\n" "" pubnotes "${string}")
string(REGEX REPLACE "^toreview:\r?\n" "" pubnotes "${pubnotes}")
if ("${string}" STREQUAL "${pubnotes}")
message(FATAL_ERROR "${NOTES} is missing \"toreview:\" marker")
endif ("${string}" STREQUAL "${pubnotes}")
# Do "wc -l" ourselves
file(READ "${DIFF_FILE}" string)
string(REGEX MATCHALL "\n" newlines "${string}")
list(LENGTH newlines lines )
##################################################
# i#78: coding style checks
#
# Since these are on the diff, not the code, remember that there
# are two extra columns at the start of the line!
set(ugly "*** STYLE VIOLATION:")
# the old "make ugly" rules
# to make the regexps simpler I'm assuming no non-alphanums run up
# against keywords: assuming spaces delimit
string(REGEX MATCH "(TRACELOG\\([^l]|NOCHECKIN)" bad "${string}")
if (bad)
message("${ugly} remove debugging facilities: \"${bad}\"")
endif (bad)
string(REGEX MATCH "DO_ONCE\\( *{? *SYSLOG_INTERNAL" bad "${string}")
if (bad)
message("${ugly} use SYSLOG_INTERNAL_*_ONCE, not DO_ONCE(SYSLOG_: \"${bad}\"")
endif (bad)
# note that ASSERT_NOT_TESTED is already DO_ONCE so shouldn't see DO_ONCE on any ASSERT
string(REGEX MATCH "ONCE\\( *{? *ASSERT" bad "${string}")
if (bad)
message("${ugly} use ASSERT_CURIOSITY_ONCE, not DO_ONCE(ASSERT: \"${bad}\"")
endif (bad)
string(REGEX MATCH "\n[^-\\*][^\n]*\t" bad "${string}")
if (bad)
message("${ugly} clean TABs w/ M-x untabify (cat -T | grep \"\^I\" to see)")
endif (bad)
string(REGEX MATCH " *(if|while) *[^{\n]*\r?\n[^;\n]*\r?\n" bad "${string}")
if (bad)
message("${ugly} use {} for multi-line body: \"${bad}\"")
endif (bad)
string(REGEX MATCH " if *\\([^\n]*;" bad "${string}")
if (bad)
message("${ugly} put if body on separate line: \"${bad}\"")
endif (bad)
# loop bodies
string(REGEX MATCH "\n[^}]* while *\\([^\n]*;" bad "${string}")
if (bad)
message("${ugly} put loop body on separate line: \"${bad}\"")
endif (bad)
string(REGEX MATCH "\n[^ ]+} *while *\\([^\n]*;" bad "${string}")
if (bad)
message("${ugly} put loop body on separate line: \"${bad}\"")
endif (bad)
string(REGEX MATCH " do *{[^\n]*;" bad "${string}")
if (bad)
message("${ugly} put loop body on separate line: \"${bad}\"")
endif (bad)
string(REGEX MATCH " for *([^\n]*;[^\n]*;[^\n]*)[^\n]*;" bad "${string}")
if (bad)
message("${ugly} put loop body on separate line: \"${bad}\"")
endif (bad)
string(REGEX MATCH "\\( *([0-9]+|NULL) *== *[^0-9]" bad "${string}")
if (bad)
message("${ugly} (1 == var) vs. (var == 1): \"${bad}\"")
endif (bad)
# check that empty param list is declared void
# FIXME: should we only require for decls by only running on header files?
# But we do want to catch static decls.
string(REGEX MATCH "\n.. *[A-Za-z0-9][A-Za-z0-9_\\* ]+\\(\\);" bad "${string}")
if (bad)
message("${ugly} empty param list should be (void) not (): \"${bad}\"")
endif (bad)
# the old "make pretty_ugly"
# since we're only checking the diff and not the existing core we
# go ahead and apply all the rules
# Rules that should pass (and almost do, all have only a few violations left and
# no false positives). FIXME - should be cleaned up and moved to ugly xref 8806.
string(REGEX MATCH " (do|if|while|for|else|switch)\\(" bad "${string}")
if (bad)
message("${ugly} spacing, if( or if ( vs if (: \"${bad}\"")
endif (bad)
string(REGEX MATCH "[}\\);]( +)?(do|if|while|for|else|switch)[ \n]" bad "${string}")
if (bad)
message("${ugly} spacing, }else or } else vs } else: \"${bad}\"")
endif (bad)
# The old "really_ugly": see comments above.
# Rules that don't pass at all, either because of excessive violations in the
# codebase or because of false positives. FIXME - for rules with few or no false
# positives but numerous violations we could use a count or something to try and
# keep things from getting worse xref case 8806.
# no false positives, but numerous violations
# that's right, cmake regexp has no {N} so we do it the hard way
string(REGEX MATCH "\n[^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n][^\n]" bad "${string}")
if (bad)
message("${ugly} line is too long: \"${bad}\"")
endif (bad)
# FIXME: not bothering to try and catch multiple statements on one line
# at this time: look at old core/Makefile if interested. Keeping comment below
# for all the issues:
#
# With all the exemptions very few false positives, but still many violations
# (and prob. some false negatives). The first grep looks for lines with two ; not
# within quotes. 2nd, 3rd and 4th greps subtract out lines where the extra
# semicolon is prob. part of a comment. NOTE, for some reason doesn't
# work for matching before the * in the 3rd grep. 5th grep subtracts out for (;;)
# loop usage and 6th grep subtracts out the very common 'case: foo; break;' motif
# (even though it technically breaks the rule). Final grep subtracts out certain
# common macros whose usage often breaks the rule.
# only a few false positives, but numerous violations
string(REGEX MATCH "[A-Za-z0-9]\\*[^A-Za-z0-9/\\)\\(]" bad "${string}")
if (bad)
message("${ugly} instr_t* foo vs. instr_t *foo, etc.: \"${bad}\"")
endif (bad)
# only a few false positives, but numerous violations (even allowing for usage
# inside /* */ comments)
string(REGEX MATCH "//" bad "${string}")
if (bad)
message("${ugly} use C style comments: \"${bad}\"")
endif (bad)
# mostly false positives (we're too grammatical ;) due to using ; in comments
string(REGEX MATCH "/\\*[^\\*].*;[^\\*]\\*/" bad "${string}")
if (bad)
message("${ugly} no commented-out code: \"${bad}\"")
endif (bad)
# some false positives and numerous violations
string(REGEX MATCH "\n[^\\*\n]*long " bad "${string}")
if (bad)
message("${ugly} use int/uint instead of long/ulong: \"${bad}\"")
endif (bad)
# Only a few false positives and not that many violations (for the "( " rule at
# least). FIXME - these could probably be moved to pretty_ugly at some point.
string(REGEX MATCH "\\( +[^\\ ]+" bad "${string}")
if (bad)
message("${ugly} spacing, ( foo, bar ) vs. (foo, bar): \"${bad}\"")
endif (bad)
string(REGEX MATCH " \\)" bad "${string}")
if (bad)
message("${ugly} spacing, ( foo, bar ) vs. (foo, bar): \"${bad}\"")
endif (bad)
# no real false positives, but tons of violations (even ignoring asm listings)
string(REGEX MATCH "\n[^\\*-][^\n]*,[^ \r\n]" bad "${string}")
if (bad)
message("${ugly} put spaces after commas: \"${bad}\"")
endif (bad)
#
##################################################
# We commit the diff to review/ using special syntax to auto-generate
# an Issue that covers performing the review
file(WRITE "${NOTES_FILE}"
"new review:\nowner: ${REVIEWER}\nsummary: ${AUTHOR}/${year}/${LABEL}.diff\n")
file(APPEND "${NOTES_FILE}" "${pubnotes}")
file(APPEND "${NOTES_FILE}" "\nstats: ${lines} diff lines\n")
if (EXISTS "${DIFFSTAT}")
execute_process(COMMAND ${DIFFSTAT} "${DIFF_FILE}"
ERROR_QUIET OUTPUT_VARIABLE diffstat_out)
file(APPEND "${NOTES_FILE}" "${diffstat_out}")
endif (EXISTS "${DIFFSTAT}")
message(STATUS "ready to commit")
if (COMMIT)
run_svn("commit;--force-log;-F;${NOTES_LOCAL}")
message(STATUS "committed")
endif (COMMIT)
endif (REVERT)