hash: add platform-specific discard functions

Our git_hash_discard() is a bit hacky: it just calls git_hash_final() into a dummy result buffer, using the side effect that each implementation's Final() function will also free any resources. This is probably not too terrible, since generating the final hash is not that expensive and we'd mostly call discard on unusual or error code paths. But we can do better by widening the platform API a bit to add an explicit discard function. This requires an annoying amount of boilerplate: - Each algorithm needs a git_$ALGO_discard() wrapper that dereferences the union'd git_hash_ctx into the type-safe field. So sha1 + sha256 + sha1-unsafe, plus a BUG() for the unknown algo. And then these all need to be referenced in the git_hash_algo structs. - Platforms which don't do anything special to discard now need a fallback function which does nothing. And we need this for each algo (sha1, sha256, and sha1-unsafe). - Platforms which do need to discard must define their discard functions. This includes sha1/openssl, sha256/openssl, and sha256/gcrypt (no sha1-unsafe here as it sits atop the sha1/openssl functions). - Algo selection needs to point platform_*_Discard to the appropriate underlying macro, or indicate that the fallback should be used. We have a similar situation for the Clone function (where a straight memcpy() of the context struct is not enough for some platforms). I've tied Discard to the same flag used by Clone here, since they are basically the same problem: is the hash context a sequence of bytes, or does it need smart copying/discarding? It's easy to miss a case here since we don't even compile the implementations we aren't using. I've tested with each of: - no flags, which uses our internal sha1/sha256 implementations, both of which exercise the noop fallback function - OPENSSL_SHA1_UNSAFE=1, which checks that our unsafe macro redirections work - OPENSSL_SHA1=1, though you should not do that in real life! - OPENSSL_SHA256=1, passes tests with GIT_TEST_DEFAULT_HASH=sha256 - GCRYPT_SHA256=1, which likewise passes The other implementations do not set the CLONE_HELPER flag, so they treat the context as bytes and should be fine with the fallback. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Jul 2, 2026 at 04:13 UTC 600588d2aa4e3311ee476f89cd57a622ed8bf0ec
5 files changed +64 -8
hash.c
+25 -8
@@ -72,6 +72,11 @@ static void git_hash_sha1_final_oid(struct object_id *oid, struct git_hash_ctx *
72 oid->algo = GIT_HASH_SHA1;
73 }
74
75 +static void git_hash_sha1_discard(struct git_hash_ctx *ctx)
76 +{
77 + git_SHA1_Discard(&ctx->state.sha1);
78 +}
79 +
80 static void git_hash_sha1_init_unsafe(struct git_hash_ctx *ctx)
81 {
82 ctx->algop = unsafe_hash_algo(&hash_algos[GIT_HASH_SHA1]);
@@ -102,6 +107,11 @@ static void git_hash_sha1_final_oid_unsafe(struct object_id *oid, struct git_has
107 oid->algo = GIT_HASH_SHA1;
108 }
109
110 +static void git_hash_sha1_discard_unsafe(struct git_hash_ctx *ctx)
111 +{
112 + git_SHA1_Discard_unsafe(&ctx->state.sha1_unsafe);
113 +}
114 +
115 static void git_hash_sha256_init(struct git_hash_ctx *ctx)
116 {
117 ctx->algop = unsafe_hash_algo(&hash_algos[GIT_HASH_SHA256]);
@@ -135,6 +145,11 @@ static void git_hash_sha256_final_oid(struct object_id *oid, struct git_hash_ctx
145 oid->algo = GIT_HASH_SHA256;
146 }
147
148 +static void git_hash_sha256_discard(struct git_hash_ctx *ctx)
149 +{
150 + git_SHA256_Discard(&ctx->state.sha256);
151 +}
152 +
153 static void git_hash_unknown_init(struct git_hash_ctx *ctx UNUSED)
154 {
155 BUG("trying to init unknown hash");
@@ -165,6 +180,11 @@ static void git_hash_unknown_final_oid(struct object_id *oid UNUSED,
180 BUG("trying to finalize unknown hash");
181 }
182
183 +static void git_hash_unknown_discard(struct git_hash_ctx *ctx UNUSED)
184 +{
185 + BUG("trying to discard unknown hash");
186 +}
187 +
188 static const struct git_hash_algo sha1_unsafe_algo = {
189 .name = "sha1",
190 .format_id = GIT_SHA1_FORMAT_ID,
@@ -176,6 +196,7 @@ static const struct git_hash_algo sha1_unsafe_algo = {
196 .update_fn = git_hash_sha1_update_unsafe,
197 .final_fn = git_hash_sha1_final_unsafe,
198 .final_oid_fn = git_hash_sha1_final_oid_unsafe,
199 + .discard_fn = git_hash_sha1_discard_unsafe,
200 .empty_tree = &empty_tree_oid,
201 .empty_blob = &empty_blob_oid,
202 .null_oid = &null_oid_sha1,
@@ -193,6 +214,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
214 .update_fn = git_hash_unknown_update,
215 .final_fn = git_hash_unknown_final,
216 .final_oid_fn = git_hash_unknown_final_oid,
217 + .discard_fn = git_hash_unknown_discard,
218 .empty_tree = NULL,
219 .empty_blob = NULL,
220 .null_oid = NULL,
@@ -208,6 +230,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
230 .update_fn = git_hash_sha1_update,
231 .final_fn = git_hash_sha1_final,
232 .final_oid_fn = git_hash_sha1_final_oid,
233 + .discard_fn = git_hash_sha1_discard,
234 .unsafe = &sha1_unsafe_algo,
235 .empty_tree = &empty_tree_oid,
236 .empty_blob = &empty_blob_oid,
@@ -224,6 +247,7 @@ const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = {
247 .update_fn = git_hash_sha256_update,
248 .final_fn = git_hash_sha256_final,
249 .final_oid_fn = git_hash_sha256_final_oid,
250 + .discard_fn = git_hash_sha256_discard,
251 .empty_tree = &empty_tree_oid_sha256,
252 .empty_blob = &empty_blob_oid_sha256,
253 .null_oid = &null_oid_sha256,
@@ -285,14 +309,7 @@ void git_hash_final_oid(struct object_id *oid, struct git_hash_ctx *ctx)
309
310 void git_hash_discard(struct git_hash_ctx *ctx)
311 {
288 - /*
289 - * XXX Many implementations do not need to do anything here,
290 - * and a dummy final() call is wasteful. But we can't fix
291 - * that unless our implementation API exposes a discard
292 - * primitive.
293 - */
294 - unsigned char dummy[GIT_MAX_RAWSZ];
295 - git_hash_final(dummy, ctx);
312 + ctx->algop->discard_fn(ctx);
313 }
314
315 uint32_t hash_algo_by_name(const char *name)
hash.h
+21
@@ -37,6 +37,7 @@
37 # define platform_SHA1_Clone_unsafe openssl_SHA1_Clone
38 # define platform_SHA1_Update_unsafe openssl_SHA1_Update
39 # define platform_SHA1_Final_unsafe openssl_SHA1_Final
40 +# define platform_SHA1_Discard_unsafe openssl_SHA1_Discard
41 # else
42 # define platform_SHA_CTX_unsafe SHA_CTX
43 # define platform_SHA1_Init_unsafe SHA1_Init
@@ -92,6 +93,7 @@
93 # define platform_SHA1_Final_unsafe platform_SHA1_Final
94 # ifdef platform_SHA1_Clone
95 # define platform_SHA1_Clone_unsafe platform_SHA1_Clone
96 +# define platform_SHA1_Discard_unsafe platform_SHA1_Discard
97 # endif
98 # ifdef SHA1_NEEDS_CLONE_HELPER
99 # define SHA1_NEEDS_CLONE_HELPER_UNSAFE
@@ -110,9 +112,11 @@
112
113 #ifdef platform_SHA1_Clone
114 #define git_SHA1_Clone platform_SHA1_Clone
115 +#define git_SHA1_Discard platform_SHA1_Discard
116 #endif
117 #ifdef platform_SHA1_Clone_unsafe
118 # define git_SHA1_Clone_unsafe platform_SHA1_Clone_unsafe
119 +# define git_SHA1_Discard_unsafe platform_SHA1_Discard_unsafe
120 #endif
121
122 #ifndef platform_SHA256_CTX
@@ -129,6 +133,7 @@
133
134 #ifdef platform_SHA256_Clone
135 #define git_SHA256_Clone platform_SHA256_Clone
136 +#define git_SHA256_Discard platform_SHA256_Discard
137 #endif
138
139 #ifdef SHA1_MAX_BLOCK_SIZE
@@ -142,6 +147,10 @@ static inline void git_SHA1_Clone(git_SHA_CTX *dst, const git_SHA_CTX *src)
147 {
148 memcpy(dst, src, sizeof(*dst));
149 }
150 +static inline void git_SHA1_Discard(git_SHA_CTX *ctx UNUSED)
151 +{
152 + /* noop */
153 +}
154 #endif
155 #ifndef SHA1_NEEDS_CLONE_HELPER_UNSAFE
156 static inline void git_SHA1_Clone_unsafe(git_SHA_CTX_unsafe *dst,
@@ -149,6 +158,10 @@ static inline void git_SHA1_Clone_unsafe(git_SHA_CTX_unsafe *dst,
158 {
159 memcpy(dst, src, sizeof(*dst));
160 }
161 +static inline void git_SHA1_Discard_unsafe(git_SHA_CTX_unsafe *ctx UNUSED)
162 +{
163 + /* noop */
164 +}
165 #endif
166
167 #ifndef SHA256_NEEDS_CLONE_HELPER
@@ -156,6 +169,10 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s
169 {
170 memcpy(dst, src, sizeof(*dst));
171 }
172 +static inline void git_SHA256_Discard(git_SHA256_CTX *ctx UNUSED)
173 +{
174 + /* noop */
175 +}
176 #endif
177
178 /*
@@ -271,6 +288,7 @@ typedef void (*git_hash_clone_fn)(struct git_hash_ctx *dst, const struct git_has
288 typedef void (*git_hash_update_fn)(struct git_hash_ctx *ctx, const void *in, size_t len);
289 typedef void (*git_hash_final_fn)(unsigned char *hash, struct git_hash_ctx *ctx);
290 typedef void (*git_hash_final_oid_fn)(struct object_id *oid, struct git_hash_ctx *ctx);
291 +typedef void (*git_hash_discard_fn)(struct git_hash_ctx *ctx);
292
293 struct git_hash_algo {
294 /*
@@ -306,6 +324,9 @@ struct git_hash_algo {
324 /* The hash finalization function for object IDs. */
325 git_hash_final_oid_fn final_oid_fn;
326
327 + /* Discard an initialized hash without finalizing. */
328 + git_hash_discard_fn discard_fn;
329 +
330 /* The OID of the empty tree. */
331 const struct object_id *empty_tree;
332
sha1/openssl.h
+6
@@ -40,12 +40,18 @@ static inline void openssl_SHA1_Clone(struct openssl_SHA1_CTX *dst,
40 EVP_MD_CTX_copy_ex(dst->ectx, src->ectx);
41 }
42
43 +static inline void openssl_SHA1_Discard(struct openssl_SHA1_CTX *ctx)
44 +{
45 + EVP_MD_CTX_free(ctx->ectx);
46 +}
47 +
48 #ifndef platform_SHA_CTX
49 #define platform_SHA_CTX openssl_SHA1_CTX
50 #define platform_SHA1_Init openssl_SHA1_Init
51 #define platform_SHA1_Clone openssl_SHA1_Clone
52 #define platform_SHA1_Update openssl_SHA1_Update
53 #define platform_SHA1_Final openssl_SHA1_Final
54 +#define platform_SHA1_Discard openssl_SHA1_Discard
55 #endif
56
57 #endif /* SHA1_OPENSSL_H */
sha256/gcrypt.h
+6
@@ -31,10 +31,16 @@ static inline void gcrypt_SHA256_Clone(gcrypt_SHA256_CTX *dst, const gcrypt_SHA2
31 gcry_md_copy(dst, *src);
32 }
33
34 +static inline void gcrypt_SHA256_Discard(gcrypt_SHA256_CTX *ctx)
35 +{
36 + gcry_md_close(*ctx);
37 +}
38 +
39 #define platform_SHA256_CTX gcrypt_SHA256_CTX
40 #define platform_SHA256_Init gcrypt_SHA256_Init
41 #define platform_SHA256_Clone gcrypt_SHA256_Clone
42 #define platform_SHA256_Update gcrypt_SHA256_Update
43 #define platform_SHA256_Final gcrypt_SHA256_Final
44 +#define platform_SHA256_Discard gcrypt_SHA256_Discard
45
46 #endif
sha256/openssl.h
+6
@@ -40,10 +40,16 @@ static inline void openssl_SHA256_Clone(struct openssl_SHA256_CTX *dst,
40 EVP_MD_CTX_copy_ex(dst->ectx, src->ectx);
41 }
42
43 +static inline void openssl_SHA256_Discard(struct openssl_SHA256_CTX *ctx)
44 +{
45 + EVP_MD_CTX_free(ctx->ectx);
46 +}
47 +
48 #define platform_SHA256_CTX openssl_SHA256_CTX
49 #define platform_SHA256_Init openssl_SHA256_Init
50 #define platform_SHA256_Clone openssl_SHA256_Clone
51 #define platform_SHA256_Update openssl_SHA256_Update
52 #define platform_SHA256_Final openssl_SHA256_Final
53 +#define platform_SHA256_Discard openssl_SHA256_Discard
54
55 #endif /* SHA256_OPENSSL_H */