diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
index dd39a086ce..bd9eac0c80 100644
--- a/src/backend/catalog/Catalog.pm
+++ b/src/backend/catalog/Catalog.pm
@@ -67,7 +67,7 @@ sub ParseHeader
 		if (!$is_client_code)
 		{
 			# Strip C-style comments.
-			s;/\*(.|\n)*\*/;;g;
+			s;/\*(?:.|\n)*\*/;;g;
 			if (m;/\*;)
 			{
 
@@ -260,7 +260,9 @@ sub ParseData
 				# We're treating the input line as a piece of Perl, so we
 				# need to use string eval here. Tell perlcritic we know what
 				# we're doing.
-				eval '$hash_ref = ' . $_;   ## no critic (ProhibitStringyEval)
+				## no critic (ProhibitStringyEval)
+				## no critic (RequireCheckingReturnValueOfEval)
+				eval '$hash_ref = ' . $_;
 				if (!ref $hash_ref)
 				{
 					die "$input_file: error parsing line $.:\n$_\n";
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 1d5450758e..5249053ee2 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -385,7 +385,7 @@ sub set_replication_conf
 	$self->host eq $test_pghost
 	  or croak "set_replication_conf only works with the default host";
 
-	open my $hba, '>>', "$pgdata/pg_hba.conf";
+	open my $hba, '>>', "$pgdata/pg_hba.conf" || die;
 	print $hba "\n# Allow replication (set up by PostgresNode.pm)\n";
 	if ($TestLib::windows_os && !$TestLib::use_unix_sockets)
 	{
@@ -439,7 +439,7 @@ sub init
 	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
 		@{ $params{auth_extra} });
 
-	open my $conf, '>>', "$pgdata/postgresql.conf";
+	open my $conf, '>>', "$pgdata/postgresql.conf" || die;
 	print $conf "\n# Added by PostgresNode.pm\n";
 	print $conf "fsync = off\n";
 	print $conf "restart_after_crash = off\n";
@@ -1254,7 +1254,7 @@ END
 		$node->clean_node if $exit_code == 0 && TestLib::all_tests_passing();
 	}
 
-	$? = $exit_code;
+	$? = $exit_code;  ## no critic (RequireLocalizedPunctuationVars)
 }
 
 =pod
@@ -1462,8 +1462,8 @@ sub psql
 	# https://metacpan.org/pod/release/ETHER/Try-Tiny-0.24/lib/Try/Tiny.pm
 	do
 	{
-		local $@;
-		eval {
+		local $@ = "";
+		eval { ## no critic (RequireCheckingReturnValueOfEval)
 			my @ipcrun_opts = (\@psql_params, '<', \$sql);
 			push @ipcrun_opts, '>',  $stdout if defined $stdout;
 			push @ipcrun_opts, '2>', $stderr if defined $stderr;
@@ -2074,8 +2074,8 @@ sub pg_recvlogical_upto
 
 	do
 	{
-		local $@;
-		eval {
+		local $@ = "";
+		eval { ## no critic (RequireCheckingReturnValueOfEval)
 			IPC::Run::run(\@cmd, ">", \$stdout, "2>", \$stderr, $timeout);
 			$ret = $?;
 		};
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 0e6c4819e4..fd3bbc1979 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -162,6 +162,8 @@ INIT
 	open my $testlog, '>', $test_logfile
 	  or die "could not open STDOUT to logfile \"$test_logfile\": $!";
 
+	# don't need to  check the result of these dup operations
+	## no critic (RequireCheckedOpen)
 	# Hijack STDOUT and STDERR to the log file
 	open(my $orig_stdout, '>&', \*STDOUT);
 	open(my $orig_stderr, '>&', \*STDERR);
@@ -409,7 +411,7 @@ Return the full contents of the specified file.
 sub slurp_file
 {
 	my ($filename) = @_;
-	local $/;
+	local $/ = undef;
 	my $contents;
 	if ($Config{osname} ne 'MSWin32')
 	{
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index e65ac6fc66..34797d97c3 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -12,9 +12,8 @@ use File::Basename;
 use File::Copy;
 use File::Find ();
 
-use Exporter;
-our (@ISA, @EXPORT_OK);
-@ISA       = qw(Exporter);
+use Exporter qw(import);
+our (@EXPORT_OK);
 @EXPORT_OK = qw(Install);
 
 my $insttype;
@@ -45,7 +44,7 @@ sub lcopy
 
 sub Install
 {
-	$| = 1;
+	STDOUT->autoflush(1);
 
 	my $target = shift;
 	$insttype = shift;
@@ -56,9 +55,8 @@ sub Install
 	our $config = shift;
 	unless ($config)
 	{
-
 		# suppress warning about harmless redeclaration of $config
-		no warnings 'misc';
+		no warnings 'misc'; ## no critic (ProhibitNoWarnings)
 		do "./config_default.pl";
 		do "./config.pl" if (-f "config.pl");
 	}
@@ -158,7 +156,7 @@ sub Install
 		File::Find::find(
 			{
 				wanted => sub {
-					/^(.*--.*\.sql|.*\.control)\z/s
+					/^(?:.*--.*\.sql|.*\.control)\z/s
 					  && push(@$pl_extension_files, $File::Find::name);
 
 					# Don't find files of in-tree temporary installations.
diff --git a/src/tools/msvc/MSBuildProject.pm b/src/tools/msvc/MSBuildProject.pm
index ebb169e201..aaa3d573ab 100644
--- a/src/tools/msvc/MSBuildProject.pm
+++ b/src/tools/msvc/MSBuildProject.pm
@@ -1,3 +1,6 @@
+
+## no critic (ProhibitMultiplePackages,ProhibitUnusedPrivateSubroutines)
+
 package MSBuildProject;
 
 #
@@ -11,7 +14,7 @@ use strict;
 use warnings;
 use base qw(Project);
 
-no warnings qw(redefine);    ## no critic
+no warnings qw(redefine);    ## no critic (ProhibitNoWarnings)
 
 sub _new
 {
@@ -145,8 +148,11 @@ EOF
 	{
 		confess "Bad format filename '$fileNameWithPath'\n"
 		  unless ($fileNameWithPath =~ m!^(.*)/([^/]+)\.(c|cpp|y|l|rc)$!);
+		# perlcritic is a bit stupid here
+		## no critic (ProhibitCaptureWithoutTest)
 		my $dir      = $1;
 		my $fileName = $2;
+		## use critic
 		if ($fileNameWithPath =~ /\.y$/ or $fileNameWithPath =~ /\.l$/)
 		{
 			push @grammarFiles, $fileNameWithPath;
@@ -415,7 +421,7 @@ use strict;
 use warnings;
 use base qw(MSBuildProject);
 
-no warnings qw(redefine);    ## no critic
+no warnings qw(redefine);    ## no critic (ProhibitNoWarnings)
 
 sub new
 {
@@ -440,7 +446,7 @@ use strict;
 use warnings;
 use base qw(MSBuildProject);
 
-no warnings qw(redefine);    ## no critic
+no warnings qw(redefine);    ## no critic  (ProhibitNoWarnings)
 
 sub new
 {
@@ -465,7 +471,7 @@ use strict;
 use warnings;
 use base qw(MSBuildProject);
 
-no warnings qw(redefine);    ## no critic
+no warnings qw(redefine);    ## no critic  (ProhibitNoWarnings)
 
 sub new
 {
@@ -490,7 +496,7 @@ use strict;
 use warnings;
 use base qw(MSBuildProject);
 
-no warnings qw(redefine);    ## no critic
+no warnings qw(redefine);    ## no critic  (ProhibitNoWarnings)
 
 sub new
 {
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 72a21dbd41..7f67c3582f 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -18,9 +18,8 @@ use Config;
 use VSObjectFactory;
 use List::Util qw(first);
 
-use Exporter;
-our (@ISA, @EXPORT_OK);
-@ISA       = qw(Exporter);
+use Exporter qw(import);
+our (@EXPORT_OK);
 @EXPORT_OK = qw(Mkvcbuild);
 
 my $solution;
@@ -106,9 +105,9 @@ sub mkvcbuild
 	  sprompt.c strerror.c tar.c thread.c
 	  win32env.c win32error.c win32security.c win32setlocale.c);
 
-	push(@pgportfiles, 'strtof.c') if ($vsVersion < '14.00');
+	push(@pgportfiles, 'strtof.c') if ($vsVersion < 14.00);
 
-	if ($vsVersion >= '9.00')
+	if ($vsVersion >= 9.00)
 	{
 		push(@pgportfiles, 'pg_crc32c_sse42_choose.c');
 		push(@pgportfiles, 'pg_crc32c_sse42.c');
@@ -212,7 +211,7 @@ sub mkvcbuild
 	$snowball->RelocateFiles(
 		'src/backend/snowball/libstemmer',
 		sub {
-			return shift !~ /(dict_snowball.c|win32ver.rc)$/;
+			return shift !~ /(?:dict_snowball.c|win32ver.rc)$/;
 		});
 	$snowball->AddIncludeDir('src/include/snowball');
 	$snowball->AddReference($postgres);
@@ -598,6 +597,7 @@ sub mkvcbuild
 			unlink $source_file;
 			open my $o, '>', $source_file
 			  || croak "Could not write to $source_file";
+			## no critic (ProhibitHardTabs)
 			print $o '
 	/* compare to plperl.h */
 	#define __inline__ __inline
@@ -627,6 +627,7 @@ sub mkvcbuild
 		}
 	}
 ';
+			## use critic
 			close $o;
 
 			# Build $source_file with a given #define, and return a true value
@@ -649,8 +650,7 @@ sub mkvcbuild
 
 					# Some builds exhibit runtime failure through Perl warning
 					# 'Can't spawn "conftest.exe"'; suppress that.
-					no warnings;
-
+					no warnings; ## no critic (ProhibitNoWarnings)
 					no strict 'subs'; ## no critic (ProhibitNoStrict)
 
 					# Disable error dialog boxes like we do in the postmaster.
diff --git a/src/tools/msvc/Project.pm b/src/tools/msvc/Project.pm
index 20f79b382b..c2e82960aa 100644
--- a/src/tools/msvc/Project.pm
+++ b/src/tools/msvc/Project.pm
@@ -10,7 +10,7 @@ use strict;
 use warnings;
 use File::Basename;
 
-sub _new
+sub _new  ## no critic (ProhibitUnusedPrivateSubroutines)
 {
 	my ($classname, $name, $type, $solution) = @_;
 	my $good_types = {
@@ -278,6 +278,8 @@ sub AddDir
 		my @pieces = split /\s+/, $match;
 		foreach my $fn (@pieces)
 		{
+			# Deliberately ignore errors from ReplaceFile about files not found
+			## no critic (RequireCheckingReturnValueOfEval)
 			if ($top eq "(top_srcdir)")
 			{
 				eval { $self->ReplaceFile($fn, $target) };
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 545bdcef7b..d9550c3e9e 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -1,3 +1,6 @@
+
+## no critic (ProhibitMultiplePackages,ProhibitUnusedPrivateSubroutines)
+
 package Solution;
 
 #
@@ -10,7 +13,7 @@ use strict;
 use warnings;
 use VSObjectFactory;
 
-no warnings qw(redefine);    ## no critic
+no warnings qw(redefine);    ## no critic  (ProhibitNoWarnings)
 
 sub _new
 {
@@ -157,20 +160,22 @@ sub GenerateFiles
 	  || confess("Could not open configure.in for reading\n");
 	while (<$c>)
 	{
-		if (/^AC_INIT\(\[([^\]]+)\], \[([^\]]+)\], \[([^\]]+)\], \[([^\]]*)\], \[([^\]]+)\]/)
+		if (/^AC_INIT\(\[([^\]]+)\], \[([^\]]+)\], \[([^\]]+)\], \[(?:[^\]]*)\], \[([^\]]+)\]/)
 		{
 			$ac_init_found = 1;
 
 			$package_name      = $1;
 			$package_version   = $2;
 			$package_bugreport = $3;
-			#$package_tarname   = $4;
-			$package_url       = $5;
+			#$package_tarname   = non-capturing-group;
+			$package_url       = $4;
 
 			if ($package_version !~ /^(\d+)(?:\.(\d+))?/)
 			{
 				confess "Bad format of version: $self->{strver}\n";
 			}
+			# perlcritic is a bit stupid here
+			## no critic (ProhibitCaptureWithoutTest)
 			$majorver = sprintf("%d", $1);
 			$minorver = sprintf("%d", $2 ? $2 : 0);
 		}
@@ -519,7 +524,7 @@ sub GenerateFiles
 		my ($digit1, $digit2, $digit3) = $self->GetOpenSSLVersion();
 
 		# More symbols are needed with OpenSSL 1.1.0 and above.
-		if ($digit1 >= '1' && $digit2 >= '1' && $digit3 >= '0')
+		if ($digit1 >= 1 && $digit2 >= 1 && $digit3 >= 0)
 		{
 			$define{HAVE_ASN1_STRING_GET0_DATA} = 1;
 			$define{HAVE_BIO_GET_DATA}          = 1;
@@ -931,7 +936,7 @@ sub AddProject
 		# changed their library names from:
 		# - libeay to libcrypto
 		# - ssleay to libssl
-		if ($digit1 >= '1' && $digit2 >= '1' && $digit3 >= '0')
+		if ($digit1 >= 1 && $digit2 >= 1 && $digit3 >= 0)
 		{
 			my $dbgsuffix;
 			my $libsslpath;
@@ -1166,7 +1171,7 @@ use strict;
 use warnings;
 use base qw(Solution);
 
-no warnings qw(redefine);    ## no critic
+no warnings qw(redefine);    ## no critic  (ProhibitNoWarnings)
 
 sub new
 {
@@ -1194,7 +1199,7 @@ use strict;
 use warnings;
 use base qw(Solution);
 
-no warnings qw(redefine);    ## no critic
+no warnings qw(redefine);    ## no critic  (ProhibitNoWarnings)
 
 sub new
 {
@@ -1222,7 +1227,7 @@ use strict;
 use warnings;
 use base qw(Solution);
 
-no warnings qw(redefine);    ## no critic
+no warnings qw(redefine);    ## no critic  (ProhibitNoWarnings)
 
 sub new
 {
@@ -1250,7 +1255,7 @@ use strict;
 use warnings;
 use base qw(Solution);
 
-no warnings qw(redefine);    ## no critic
+no warnings qw(redefine);    ## no critic  (ProhibitNoWarnings)
 
 sub new
 {
diff --git a/src/tools/msvc/VSObjectFactory.pm b/src/tools/msvc/VSObjectFactory.pm
index e6983b241f..dd8cc4952e 100644
--- a/src/tools/msvc/VSObjectFactory.pm
+++ b/src/tools/msvc/VSObjectFactory.pm
@@ -1,3 +1,6 @@
+
+## no critic (ProhibitMultiplePackages)
+
 package VSObjectFactory;
 
 #
@@ -10,16 +13,15 @@ use Carp;
 use strict;
 use warnings;
 
-use Exporter;
+use Exporter qw(import);
 use Project;
 use Solution;
 use MSBuildProject;
 
-our (@ISA, @EXPORT);
-@ISA    = qw(Exporter);
+our (@EXPORT);
 @EXPORT = qw(CreateSolution CreateProject DetermineVisualStudioVersion);
 
-no warnings qw(redefine);    ## no critic
+no warnings qw(redefine);    ## no critic  (ProhibitNoWarnings)
 
 sub CreateSolution
 {
diff --git a/src/tools/perlcheck/perlcriticrc b/src/tools/perlcheck/perlcriticrc
index e230111b23..27b4af1892 100644
--- a/src/tools/perlcheck/perlcriticrc
+++ b/src/tools/perlcheck/perlcriticrc
@@ -22,3 +22,30 @@ verbose = %f: %m at line %l, column %c.  %e.  ([%p] Severity: %s)\n
 # insist on use of the warnings pragma
 [TestingAndDebugging::RequireUseWarnings]
 severity = 5
+
+
+# sev 4
+[-Modules::ProhibitAutomaticExportation]
+[-InputOutput::RequireBriefOpen]
+[-Subroutines::RequireArgUnpacking]
+[Variables::RequireLocalizedPunctuationVars]
+allow = %ENV %SIG
+[-Objects::ProhibitIndirectSyntax]
+[TestingAndDebugging::ProhibitProlongedStrictureOverride]
+statements = 10
+[-BuiltinFunctions::RequireBlockGrep]
+[TestingAndDebugging::ProhibitNoWarnings]
+allow = once
+
+# sev 3
+[-ErrorHandling::RequireCarping]
+[-RegularExpressions::RequireExtendedFormatting]
+[-Variables::ProhibitPackageVars]
+[-ControlStructures::ProhibitCascadingIfElse]
+[-Subroutines::ProhibitExcessComplexity]
+[-ValuesAndExpressions::ProhibitImplicitNewlines]
+[-Subroutines::ProhibitManyArgs]
+[-InputOutput::ProhibitBacktickOperators]
+[-BuiltinFunctions::ProhibitLvalueSubstr]
+[-ValuesAndExpressions::RequireQuotedHeredocTerminator]
+[-RegularExpressions::ProhibitComplexRegexes]
diff --git a/src/tools/perlcheck/pgperlcritic b/src/tools/perlcheck/pgperlcritic
index 1c2f787580..08edd86427 100755
--- a/src/tools/perlcheck/pgperlcritic
+++ b/src/tools/perlcheck/pgperlcritic
@@ -14,7 +14,21 @@ PERLCRITIC=${PERLCRITIC:-perlcritic}
 
 . src/tools/perlcheck/find_perl_files
 
-find_perl_files | xargs $PERLCRITIC \
+flist=`mktemp`
+find_perl_files > $flist
+
+pattern='src/test/perl/|src/backend/catalog/Catalog.pm|src/tools/msvc/[^/]*.pm'
+
+# normal sev 5 critic
+egrep -v  "$pattern" < $flist | xargs $PERLCRITIC \
 	  --quiet \
 	  --program-extensions .pl \
 	  --profile=src/tools/perlcheck/perlcriticrc
+
+# more strict sev 3 critic for some library files
+egrep  "$pattern" < $flist | xargs $PERLCRITIC --severity 3 \
+	  --quiet \
+	  --program-extensions .pl \
+	  --profile=src/tools/perlcheck/perlcriticrc
+
+rm -f $flist
