1 // Copyright 2021 Code Intelligence GmbH 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.code_intelligence.jazzer.utils 16 17 private val BASE_INCLUDED_CLASS_NAME_GLOBS = listOf( 18 "**", // everything 19 ) 20 21 // We use both a strong indicator for running as a Bazel test together with an indicator for a 22 // Bazel coverage run to rule out false positives. 23 private val IS_BAZEL_COVERAGE_RUN = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR") != null && 24 System.getenv("COVERAGE_DIR") != null 25 26 private val ADDITIONAL_EXCLUDED_NAME_GLOBS_FOR_BAZEL_COVERAGE = listOf( 27 "com.google.testing.coverage.**", 28 "org.jacoco.**", 29 ) 30 31 private val BASE_EXCLUDED_CLASS_NAME_GLOBS = listOf( 32 // JDK internals 33 "\\[**", // array types 34 "java.**", 35 "javax.**", 36 "jdk.**", 37 "sun.**", 38 "com.sun.**", // package for Proxy objects 39 // Azul JDK internals 40 "com.azul.tooling.**", 41 // Kotlin internals 42 "kotlin.**", 43 // Jazzer internals 44 "com.code_intelligence.jazzer.**", 45 "jaz.Ter", // safe companion of the honeypot class used by sanitizers 46 "jaz.Zer", // honeypot class used by sanitizers 47 ) + if (IS_BAZEL_COVERAGE_RUN) ADDITIONAL_EXCLUDED_NAME_GLOBS_FOR_BAZEL_COVERAGE else listOf() 48 49 class ClassNameGlobber(includes: List<String>, excludes: List<String>) { 50 // If no include globs are provided, start with all classes. <lambda>null51 private val includeMatchers = includes.ifEmpty { BASE_INCLUDED_CLASS_NAME_GLOBS } 52 .map(::SimpleGlobMatcher) 53 54 // If no include globs are provided, additionally exclude stdlib classes as well as our own classes. 55 private val excludeMatchers = (if (includes.isEmpty()) BASE_EXCLUDED_CLASS_NAME_GLOBS + excludes else excludes) 56 .map(::SimpleGlobMatcher) 57 includesnull58 fun includes(className: String): Boolean { 59 return includeMatchers.any { it.matches(className) } && excludeMatchers.none { it.matches(className) } 60 } 61 } 62 63 class SimpleGlobMatcher(val glob: String) { 64 private enum class Type { 65 // foo.bar (matches foo.bar only) 66 FULL_MATCH, 67 // foo.** (matches foo.bar and foo.bar.baz) 68 PATH_WILDCARD_SUFFIX, 69 // foo.* (matches foo.bar, but not foo.bar.baz) 70 SEGMENT_WILDCARD_SUFFIX, 71 } 72 73 private val type: Type 74 private val prefix: String 75 76 init { 77 // Remain compatible with globs such as "\\[" that use escaping. 78 val pattern = glob.replace("\\", "") 79 when { 80 !pattern.contains('*') -> { 81 type = Type.FULL_MATCH 82 prefix = pattern 83 } 84 // Ends with "**" and contains no other '*'. 85 pattern.endsWith("**") && pattern.indexOf('*') == pattern.length - 2 -> { 86 type = Type.PATH_WILDCARD_SUFFIX 87 prefix = pattern.removeSuffix("**") 88 } 89 // Ends with "*" and contains no other '*'. 90 pattern.endsWith('*') && pattern.indexOf('*') == pattern.length - 1 -> { 91 type = Type.SEGMENT_WILDCARD_SUFFIX 92 prefix = pattern.removeSuffix("*") 93 } 94 else -> throw IllegalArgumentException( 95 "Unsupported glob pattern (only foo.bar, foo.* and foo.** are supported): $pattern" 96 ) 97 } 98 } 99 100 /** 101 * Checks whether [maybeInternalClassName], which may be internal (foo/bar) or not (foo.bar), matches [glob]. 102 */ matchesnull103 fun matches(maybeInternalClassName: String): Boolean { 104 val className = maybeInternalClassName.replace('/', '.') 105 return when (type) { 106 Type.FULL_MATCH -> className == prefix 107 Type.PATH_WILDCARD_SUFFIX -> className.startsWith(prefix) 108 Type.SEGMENT_WILDCARD_SUFFIX -> { 109 // className starts with prefix and contains no further '.'. 110 className.startsWith(prefix) && 111 className.indexOf('.', startIndex = prefix.length) == -1 112 } 113 } 114 } 115 } 116