libselinux: improve performance with pcre matches am: 72806f3933 am: 0470684f9e am: 4d3b040b58

Original change: https://android-review.googlesource.com/c/platform/external/selinux/+/2411194

Change-Id: I73964710fcec1688fdb4fb1d1706428c4244d95d
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/libselinux/src/regex.c b/libselinux/src/regex.c
index 16df679..54f2402 100644
--- a/libselinux/src/regex.c
+++ b/libselinux/src/regex.c
@@ -30,6 +30,11 @@
 #endif
 
 #ifdef USE_PCRE2
+static pthread_key_t match_data_key;
+static int match_data_key_initialized = -1;
+static pthread_mutex_t key_mutex = PTHREAD_MUTEX_INITIALIZER;
+static __thread char match_data_initialized;
+
 char const *regex_arch_string(void)
 {
 	static char arch_string_buffer[32];
@@ -60,14 +65,6 @@
 
 struct regex_data {
 	pcre2_code *regex; /* compiled regular expression */
-#ifndef AGGRESSIVE_FREE_AFTER_REGEX_MATCH
-	/*
-	 * match data block required for the compiled
-	 * pattern in pcre2
-	 */
-	pcre2_match_data *match_data;
-#endif
-	pthread_mutex_t match_mutex;
 };
 
 int regex_prepare_data(struct regex_data **regex, char const *pattern_string,
@@ -86,13 +83,6 @@
 		goto err;
 	}
 
-#ifndef AGGRESSIVE_FREE_AFTER_REGEX_MATCH
-	(*regex)->match_data =
-	    pcre2_match_data_create_from_pattern((*regex)->regex, NULL);
-	if (!(*regex)->match_data) {
-		goto err;
-	}
-#endif
 	return 0;
 
 err:
@@ -142,13 +132,6 @@
 		if (rc != 1)
 			goto err;
 
-#ifndef AGGRESSIVE_FREE_AFTER_REGEX_MATCH
-		(*regex)->match_data =
-		    pcre2_match_data_create_from_pattern((*regex)->regex, NULL);
-		if (!(*regex)->match_data)
-			goto err;
-#endif
-
 		*regex_compiled = true;
 	}
 
@@ -204,18 +187,32 @@
 	return rc;
 }
 
+static void __attribute__((destructor)) match_data_thread_free(void *key)
+{
+	void *value;
+	pcre2_match_data *match_data;
+
+	if (match_data_key_initialized <= 0 || !match_data_initialized)
+		return;
+
+	value = __selinux_getspecific(match_data_key);
+	match_data = value ? value : key;
+
+	pcre2_match_data_free(match_data);
+
+	__pthread_mutex_lock(&key_mutex);
+	if (--match_data_key_initialized == 1) {
+		__selinux_key_delete(match_data_key);
+		match_data_key_initialized = -1;
+	}
+	__pthread_mutex_unlock(&key_mutex);
+}
+
 void regex_data_free(struct regex_data *regex)
 {
 	if (regex) {
 		if (regex->regex)
 			pcre2_code_free(regex->regex);
-
-#ifndef AGGRESSIVE_FREE_AFTER_REGEX_MATCH
-		if (regex->match_data)
-			pcre2_match_data_free(regex->match_data);
-#endif
-
-		__pthread_mutex_destroy(&regex->match_mutex);
 		free(regex);
 	}
 }
@@ -223,32 +220,40 @@
 int regex_match(struct regex_data *regex, char const *subject, int partial)
 {
 	int rc;
-	pcre2_match_data *match_data;
-	__pthread_mutex_lock(&regex->match_mutex);
+	bool slow;
+	pcre2_match_data *match_data = NULL;
 
-#ifdef AGGRESSIVE_FREE_AFTER_REGEX_MATCH
-	match_data = pcre2_match_data_create_from_pattern(
-	    regex->regex, NULL);
-	if (match_data == NULL) {
-		__pthread_mutex_unlock(&regex->match_mutex);
-		return REGEX_ERROR;
+	if (match_data_key_initialized > 0) {
+		if (match_data_initialized == 0) {
+			match_data = pcre2_match_data_create(1, NULL);
+			if (match_data) {
+				match_data_initialized = 1;
+				__selinux_setspecific(match_data_key,
+							match_data);
+				__pthread_mutex_lock(&key_mutex);
+				match_data_key_initialized++;
+				__pthread_mutex_unlock(&key_mutex);
+			}
+		} else
+			match_data = __selinux_getspecific(match_data_key);
 	}
-#else
-	match_data = regex->match_data;
-#endif
+
+	slow = (match_data_key_initialized <= 0 || match_data == NULL);
+	if (slow) {
+		match_data = pcre2_match_data_create_from_pattern(regex->regex,
+									NULL);
+		if (!match_data)
+			return REGEX_ERROR;
+	}
 
 	rc = pcre2_match(
 	    regex->regex, (PCRE2_SPTR)subject, PCRE2_ZERO_TERMINATED, 0,
 	    partial ? PCRE2_PARTIAL_SOFT : 0, match_data, NULL);
 
-#ifdef AGGRESSIVE_FREE_AFTER_REGEX_MATCH
-	// pcre2_match allocates heap and it won't be freed until
-	// pcre2_match_data_free, resulting in heap overhead.
-	pcre2_match_data_free(match_data);
-#endif
+	if (slow)
+		pcre2_match_data_free(match_data);
 
-	__pthread_mutex_unlock(&regex->match_mutex);
-	if (rc > 0)
+	if (rc >= 0)
 		return REGEX_MATCH;
 	switch (rc) {
 	case PCRE2_ERROR_PARTIAL:
@@ -290,7 +295,14 @@
 	if (!regex_data)
 		return NULL;
 
-	__pthread_mutex_init(&regex_data->match_mutex, NULL);
+	__pthread_mutex_lock(&key_mutex);
+	if (match_data_key_initialized < 0) {
+		match_data_key_initialized = !__selinux_key_create(
+							&match_data_key,
+							match_data_thread_free);
+	}
+	__pthread_mutex_unlock(&key_mutex);
+
 	return regex_data;
 }
 
diff --git a/libselinux/src/selinux_internal.h b/libselinux/src/selinux_internal.h
index 06f2c03..d1e6c50 100644
--- a/libselinux/src/selinux_internal.h
+++ b/libselinux/src/selinux_internal.h
@@ -13,6 +13,7 @@
 #pragma weak pthread_key_create
 #pragma weak pthread_key_delete
 #pragma weak pthread_setspecific
+#pragma weak pthread_getspecific
 
 /* Call handler iff the first call.  */
 #define __selinux_once(ONCE_CONTROL, INIT_FUNCTION)	\
@@ -41,6 +42,9 @@
 			pthread_setspecific(KEY, VALUE);	\
 	} while (0)
 
+#define __selinux_getspecific(KEY)				\
+	(pthread_getspecific != NULL ? pthread_getspecific(KEY) : NULL)
+
 /* selabel_lookup() is only thread safe if we're compiled with pthreads */
 
 #pragma weak pthread_mutex_init