1 /*
2    +----------------------------------------------------------------------+
3    | Zend OPcache                                                         |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | https://www.php.net/license/3_01.txt                                 |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Andi Gutmans <andi@php.net>                                 |
16    |          Zeev Suraski <zeev@php.net>                                 |
17    |          Stanislav Malyshev <stas@zend.com>                          |
18    |          Dmitry Stogov <dmitry@php.net>                              |
19    +----------------------------------------------------------------------+
20 */
21 
22 #include "ZendAccelerator.h"
23 #include "zend_accelerator_hash.h"
24 #include "zend_hash.h"
25 #include "zend_shared_alloc.h"
26 
27 /* Generated on an Octa-ALPHA 300MHz CPU & 2.5GB RAM monster */
28 static const uint32_t prime_numbers[] =
29 	{5, 11, 19, 53, 107, 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 };
30 static const uint32_t num_prime_numbers = sizeof(prime_numbers) / sizeof(uint32_t);
31 
zend_accel_hash_clean(zend_accel_hash * accel_hash)32 void zend_accel_hash_clean(zend_accel_hash *accel_hash)
33 {
34 	accel_hash->num_entries = 0;
35 	accel_hash->num_direct_entries = 0;
36 	memset(accel_hash->hash_table, 0, sizeof(zend_accel_hash_entry *)*accel_hash->max_num_entries);
37 }
38 
zend_accel_hash_init(zend_accel_hash * accel_hash,uint32_t hash_size)39 void zend_accel_hash_init(zend_accel_hash *accel_hash, uint32_t hash_size)
40 {
41 	uint32_t i;
42 
43 	for (i=0; i<num_prime_numbers; i++) {
44 		if (hash_size <= prime_numbers[i]) {
45 			hash_size = prime_numbers[i];
46 			break;
47 		}
48 	}
49 
50 	accel_hash->num_entries = 0;
51 	accel_hash->num_direct_entries = 0;
52 	accel_hash->max_num_entries = hash_size;
53 
54 	/* set up hash pointers table */
55 	accel_hash->hash_table = zend_shared_alloc(sizeof(zend_accel_hash_entry *)*accel_hash->max_num_entries);
56 	if (!accel_hash->hash_table) {
57 		zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Insufficient shared memory!");
58 		return;
59 	}
60 
61 	/* set up hash values table */
62 	accel_hash->hash_entries = zend_shared_alloc(sizeof(zend_accel_hash_entry)*accel_hash->max_num_entries);
63 	if (!accel_hash->hash_entries) {
64 		zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Insufficient shared memory!");
65 		return;
66 	}
67 	memset(accel_hash->hash_table, 0, sizeof(zend_accel_hash_entry *)*accel_hash->max_num_entries);
68 }
69 
70 /* Returns NULL if hash is full
71  * Returns pointer the actual hash entry on success
72  * key needs to be already allocated as it is not copied
73  */
zend_accel_hash_update(zend_accel_hash * accel_hash,zend_string * key,bool indirect,void * data)74 zend_accel_hash_entry* zend_accel_hash_update(zend_accel_hash *accel_hash, zend_string *key, bool indirect, void *data)
75 {
76 	zend_ulong hash_value;
77 	zend_ulong index;
78 	zend_accel_hash_entry *entry;
79 	zend_accel_hash_entry *indirect_bucket = NULL;
80 
81 	if (indirect) {
82 		indirect_bucket = (zend_accel_hash_entry*)data;
83 		while (indirect_bucket->indirect) {
84 			indirect_bucket = (zend_accel_hash_entry*)indirect_bucket->data;
85 		}
86 	}
87 
88 	hash_value = zend_string_hash_val(key);
89 #ifndef ZEND_WIN32
90 	hash_value ^= ZCG(root_hash);
91 #endif
92 	index = hash_value % accel_hash->max_num_entries;
93 
94 	/* try to see if the element already exists in the hash */
95 	entry = accel_hash->hash_table[index];
96 	while (entry) {
97 		if (entry->hash_value == hash_value
98 		 && zend_string_equals(entry->key, key)) {
99 
100 			if (entry->indirect) {
101 				if (indirect_bucket) {
102 					entry->data = indirect_bucket;
103 				} else {
104 					((zend_accel_hash_entry*)entry->data)->data = data;
105 				}
106 			} else {
107 				if (indirect_bucket) {
108 					accel_hash->num_direct_entries--;
109 					entry->data = indirect_bucket;
110 					entry->indirect = 1;
111 				} else {
112 					entry->data = data;
113 				}
114 			}
115 			return entry;
116 		}
117 		entry = entry->next;
118 	}
119 
120 	/* Does not exist, add a new entry */
121 	if (accel_hash->num_entries == accel_hash->max_num_entries) {
122 		return NULL;
123 	}
124 
125 	entry = &accel_hash->hash_entries[accel_hash->num_entries++];
126 	if (indirect) {
127 		entry->data = indirect_bucket;
128 		entry->indirect = 1;
129 	} else {
130 		accel_hash->num_direct_entries++;
131 		entry->data = data;
132 		entry->indirect = 0;
133 	}
134 	entry->hash_value = hash_value;
135 	entry->key = key;
136 	entry->next = accel_hash->hash_table[index];
137 	accel_hash->hash_table[index] = entry;
138 	return entry;
139 }
140 
zend_accel_hash_find_ex(zend_accel_hash * accel_hash,zend_string * key,int data)141 static zend_always_inline void* zend_accel_hash_find_ex(zend_accel_hash *accel_hash, zend_string *key, int data)
142 {
143 	zend_ulong index;
144 	zend_accel_hash_entry *entry;
145 	zend_ulong hash_value;
146 
147 	hash_value = zend_string_hash_val(key);
148 #ifndef ZEND_WIN32
149 	hash_value ^= ZCG(root_hash);
150 #endif
151 	index = hash_value % accel_hash->max_num_entries;
152 
153 	entry = accel_hash->hash_table[index];
154 	while (entry) {
155 		if (entry->hash_value == hash_value
156 		 && zend_string_equals(entry->key, key)) {
157 			if (entry->indirect) {
158 				if (data) {
159 					return ((zend_accel_hash_entry*)entry->data)->data;
160 				} else {
161 					return entry->data;
162 				}
163 			} else {
164 				if (data) {
165 					return entry->data;
166 				} else {
167 					return entry;
168 				}
169 			}
170 		}
171 		entry = entry->next;
172 	}
173 	return NULL;
174 }
175 
176 /* Returns the data associated with key on success
177  * Returns NULL if data doesn't exist
178  */
zend_accel_hash_find(zend_accel_hash * accel_hash,zend_string * key)179 void* zend_accel_hash_find(zend_accel_hash *accel_hash, zend_string *key)
180 {
181 	return zend_accel_hash_find_ex(accel_hash, key, 1);
182 }
183 
184 /* Returns the hash entry associated with key on success
185  * Returns NULL if it doesn't exist
186  */
zend_accel_hash_find_entry(zend_accel_hash * accel_hash,zend_string * key)187 zend_accel_hash_entry* zend_accel_hash_find_entry(zend_accel_hash *accel_hash, zend_string *key)
188 {
189 	return (zend_accel_hash_entry *)zend_accel_hash_find_ex(accel_hash, key, 0);
190 }
191 
zend_accel_hash_unlink(zend_accel_hash * accel_hash,zend_string * key)192 int zend_accel_hash_unlink(zend_accel_hash *accel_hash, zend_string *key)
193 {
194 	zend_ulong hash_value;
195 	zend_ulong index;
196 	zend_accel_hash_entry *entry, *last_entry=NULL;
197 
198 	hash_value = zend_string_hash_val(key);
199 #ifndef ZEND_WIN32
200 	hash_value ^= ZCG(root_hash);
201 #endif
202 	index = hash_value % accel_hash->max_num_entries;
203 
204 	entry = accel_hash->hash_table[index];
205 	while (entry) {
206 		if (entry->hash_value == hash_value
207 		 && zend_string_equals(entry->key, key)) {
208 			if (!entry->indirect) {
209 				accel_hash->num_direct_entries--;
210 			}
211 			if (last_entry) {
212 				last_entry->next = entry->next;
213 			} else {
214 				accel_hash->hash_table[index] = entry->next;
215 			}
216 			return SUCCESS;
217 		}
218 		last_entry = entry;
219 		entry = entry->next;
220 	}
221 	return FAILURE;
222 }
223