Info: memset() followed by a complete overwrite may be unnecessary. Verify that the memset is required (e.g., for security zeroing) and not redundant.
memset(rq, 0, sizeof(mcp_request_t));
1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */23#include "proxy.h"4#include "proto_parser.h"56// FIXME (v2): any reason to pass in command/cmdlen separately?7mcp_request_t *mcp_new_request(lua_State *L, mcp_parser_t *pr, const char *command, size_t cmdlen) {8 mcp_request_t *rq = lua_newuserdatauv(L, sizeof(mcp_request_t) + MCP_REQUEST_MAXLEN, 0);9 // TODO (v2): memset only the non-data part? as the rest gets memcpy'd10 // over.11 memset(rq, 0, sizeof(mcp_request_t));12 memcpy(&rq->pr, pr, sizeof(*pr));1314 memcpy(rq->request, command, cmdlen);15 rq->pr.request = rq->request;16 rq->pr.reqlen = cmdlen;1718 luaL_getmetatable(L, "mcp.request");19 lua_setmetatable(L, -2);2021 // at this point we should know if we have to bounce through _nread to22 // get item data or not.23 return rq;24}2526// fill a preallocated request object.27void mcp_set_request(mcp_parser_t *pr, mcp_request_t *rq, const char *command, size_t cmdlen) {28 memset(rq, 0, sizeof(mcp_request_t));29 memcpy(&rq->pr, pr, sizeof(*pr));3031 memcpy(rq->request, command, cmdlen);32 rq->pr.request = rq->request;33 rq->pr.reqlen = cmdlen;34}3536// Replaces a token inside a request and re-parses.37// Note that this has some optimization opportunities. Delaying until38// required.39// We should not guarantee order when updating meta flags, which would allow40// blanking tokens and appending new ones.41// TODO (v2): much of the length is the key, avoid copying it.42int mcp_request_render(mcp_request_t *rq, int idx, const char flag, const char *tok, size_t len) {43 char temp[MCP_REQUEST_MAXLEN+1];44 char *p = temp;45 mcp_parser_t *pr = &rq->pr;4647 if (pr->reqlen + len + 2 > MCP_REQUEST_MAXLEN) {48 return -1;49 }50 // Cannot add/append tokens yet.51 if (idx >= pr->tok.ntokens) {52 return -1;53 }5455 // FIXME: should use an accessor macro, at least.56 memcpy(p, pr->request, pr->tok.tokens[idx]);57 p += pr->tok.tokens[idx];5859 if (flag) {60 *p = flag;61 p++;62 }63 if (tok) {64 memcpy(p, tok, len);65 p += len;66 }6768 // Add a space and copy more tokens if there were more.69 if (idx+1 < pr->tok.ntokens) {70 if (flag || len != 0) {71 // Only pre-space if not deleting the token.72 *p = ' ';73 p++;74 }75 memcpy(p, &pr->request[pr->tok.tokens[idx+1]], pr->tok.tokens[pr->tok.ntokens] - pr->tok.tokens[idx+1]);76 p += pr->tok.tokens[pr->tok.ntokens] - pr->tok.tokens[idx+1];77 } else {78 // If we removed something from the end we might've left some spaces.79 while (p > temp && *(p-1) == ' ') {80 p--;81 }82 }8384 memcpy(p, "\r\n\0", 3);85 p += 2;8687 memcpy(rq->request, temp, p - temp);8889 // Hold the vlen/vbuf and restore after re-parsing. Since we can only edit90 // the command line, not the value here, we would otherwise allow sending91 // arbitrary memory over the network if someone modifies a SET.92 void *vbuf = pr->vbuf;93 int vlen = pr->vlen;9495 memset(pr, 0, sizeof(mcp_parser_t)); // TODO: required?96 int ret = process_request(pr, rq->request, p - temp);97 pr->vbuf = vbuf;98 pr->vlen = vlen;99100 if (ret != 0) {101 return ret;102 }103 return 0;104}105106int mcp_request_append(mcp_request_t *rq, const char flag, const char *tok, size_t len) {107 mcp_parser_t *pr = &rq->pr;108 const char *start = pr->request;109 char *p = (char *)pr->request + pr->reqlen - 2; // start at the \r110 assert(*p == '\r');111112 if (pr->reqlen + len + 2 >= MCP_REQUEST_MAXLEN) {113 return -1;114 }115116 *p = ' ';117 p++;118119 if (flag) {120 *p = flag;121 p++;122 }123 if (tok) {124 memcpy(p, tok, len);125 p += len;126 }127128 memcpy(p, "\r\n\0", 3);129 p += 2;130131 // See note on mcp_request_render()132 void *vbuf = pr->vbuf;133 int vlen = pr->vlen;134135 memset(pr, 0, sizeof(mcp_parser_t)); // TODO: required?136 int ret = process_request(pr, rq->request, p - start);137 pr->vbuf = vbuf;138 pr->vlen = vlen;139 if (ret != 0) {140 return ret;141 }142143 return 0;144}145146void mcp_request_attach(mcp_request_t *rq, io_pending_proxy_t *p) {147 mcp_parser_t *pr = &rq->pr;148 char *r = (char *) pr->request;149 size_t len = pr->reqlen;150151 // The stringified request. This is also referencing into the coroutine152 // stack, which should be safe from gc.153 p->iov[0].iov_base = r;154 p->iov[0].iov_len = len;155 p->iovcnt = 1;156 p->iovbytes = len;157 if (pr->vlen != 0) {158 p->iov[1].iov_base = pr->vbuf;159 p->iov[1].iov_len = pr->vlen;160 p->iovcnt = 2;161 p->iovbytes += pr->vlen;162 }163}164165// second argument is optional, for building set requests.166// TODO: append the \r\n for the VAL?167int mcplib_request(lua_State *L) {168 LIBEVENT_THREAD *t = PROXY_GET_THR(L);169 size_t len = 0;170 size_t vlen = 0;171 mcp_parser_t pr = {0};172 const char *cmd = luaL_checklstring(L, 1, &len);173 const char *val = NULL;174 int type = lua_type(L, 2);175 if (type == LUA_TSTRING) {176 val = luaL_optlstring(L, 2, NULL, &vlen);177 if (vlen < 2 || memcmp(val+vlen-2, "\r\n", 2) != 0) {178 proxy_lua_error(L, "value passed to mcp.request must end with \\r\\n");179 }180 } else if (type == LUA_TUSERDATA) {181 // vlen for requests and responses include the "\r\n" already.182 mcp_resp_t *r = luaL_testudata(L, 2, "mcp.response");183 if (r != NULL) {184 if (r->resp.value) {185 val = r->resp.value;186 vlen = r->resp.vlen_read; // paranoia, so we can't overread into memory.187 }188 } else {189 mcp_request_t *rq = luaL_testudata(L, 2, "mcp.request");190 if (rq->pr.vbuf) {191 val = rq->pr.vbuf;192 vlen = rq->pr.vlen;193 }194 }195 }196197 if (len > MCP_REQUEST_MAXLEN) {198 proxy_lua_error(L, "request length too long");199 return 0;200 }201202 if (memcmp(cmd+len-2, "\r\n", 2) != 0) {203 proxy_lua_error(L, "request must end with \r\n");204 return 0;205 }206207 // FIXME (v2): if we inline the userdata we can avoid memcpy'ing the parser208 // structure from the stack? but causes some code duplication.209 if (process_request(&pr, cmd, len) != 0) {210 proxy_lua_error(L, "failed to parse request");211 return 0;212 }213 mcp_request_t *rq = mcp_new_request(L, &pr, cmd, len);214215 if (val != NULL) {216 rq->pr.vlen = vlen;217 rq->pr.vbuf = malloc(vlen);218 if (rq->pr.vbuf == NULL) {219 // Note: without *c we can't tick the appropriate counter.220 // However, in practice raw malloc's are nearly never going to221 // fail.222 // TODO(v2): we can stack values into the request objects or use223 // the slabber memory, so this isn't necessary anyway.224 proxy_lua_error(L, "failed to allocate value memory for request object");225 }226 memcpy(rq->pr.vbuf, val, vlen);227 // Note: Not enforcing the memory limit here is deliberate:228 // - if we're over the memory limit, it'll get caught very soon after229 // this, but we won't be causing some lua to bail mid-flight, which is230 // more graceful to the end user.231 t->proxy_buffer_memory_used += rq->pr.vlen;232 }233234 // rq is now created, parsed, and on the stack.235 return 1;236}237238int mcplib_request_key(lua_State *L) {239 mcp_request_t *rq = luaL_checkudata(L, -1, "mcp.request");240 lua_pushlstring(L, MCP_PARSER_KEY(&rq->pr), rq->pr.klen);241 return 1;242}243244// NOTE: I've mixed up const/non-const strings in the request. During parsing245// we want it to be const, but after that's done the request is no longer246// const. It might be better to just remove the const higher up the chain, but247// I'd rather not. So for now these functions will be dumping the const to248// modify the string.249int mcplib_request_ltrimkey(lua_State *L) {250 mcp_request_t *rq = luaL_checkudata(L, -2, "mcp.request");251 int totrim = luaL_checkinteger(L, -1);252 char *key = (char *) MCP_PARSER_KEY(&rq->pr);253254 if (totrim > rq->pr.klen) {255 proxy_lua_error(L, "ltrimkey cannot zero out key");256 return 0;257 } else {258 memset(key, ' ', totrim);259 rq->pr.klen -= totrim;260 rq->pr.tok.tokens[rq->pr.keytoken] += totrim;261 }262 return 1;263}264265int mcplib_request_rtrimkey(lua_State *L) {266 mcp_request_t *rq = luaL_checkudata(L, -2, "mcp.request");267 int totrim = luaL_checkinteger(L, -1);268 char *key = (char *) MCP_PARSER_KEY(&rq->pr);269270 if (totrim > rq->pr.klen) {271 proxy_lua_error(L, "rtrimkey cannot zero out key");272 return 0;273 } else {274 memset(key + (rq->pr.klen - totrim), ' ', totrim);275 rq->pr.klen -= totrim;276 // don't need to change the key token.277 }278 return 1;279}280281int mcplib_request_raw_line(lua_State *L) {282 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");283 lua_pushlstring(L, rq->pr.request, rq->pr.reqlen-2);284 return 1;285}286287int mcplib_request_raw_value(lua_State *L) {288 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");289 if (rq->pr.vbuf) {290 lua_pushlstring(L, rq->pr.vbuf, rq->pr.vlen);291 } else {292 lua_pushnil(L);293 }294 return 1;295}296297// Virtual table operations on the request.298int mcplib_request_token(lua_State *L) {299 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");300 int argc = lua_gettop(L);301302 if (argc == 1) {303 lua_pushnil(L);304 return 1;305 }306307 int token = luaL_checkinteger(L, 2);308309 if (token < 1 || token > rq->pr.tok.ntokens) {310 // maybe an error?311 lua_pushnil(L);312 return 1;313 }314315 if (argc > 2) {316 // overwriting a token.317 size_t newlen = 0;318 const char *newtok = lua_tolstring(L, 3, &newlen);319 if (mcp_request_render(rq, token-1, 0, newtok, newlen) != 0) {320 proxy_lua_error(L, "token(): request malformed after edit");321 return 0;322 }323 return 0;324 } else {325 // fetching a token.326 mcp_parser_t *pr = &rq->pr;327 int vlen = 0;328 const char *start = mcmc_token_get(pr->request, &pr->tok, pr->keytoken, &vlen);329330 P_DEBUG("%s: pushing token of len: %lu\n", __func__, vlen);331 lua_pushlstring(L, start, vlen);332 return 1;333 }334335 return 0;336}337338// Fetch only.339int mcplib_request_token_int(lua_State *L) {340 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");341 int argc = lua_gettop(L);342343 if (argc == 1) {344 lua_pushnil(L);345 return 1;346 }347348 int x = luaL_checkinteger(L, 2);349350 if (x < 1 || x > rq->pr.tok.ntokens) {351 // maybe an error?352 lua_pushnil(L);353 return 1;354 }355356 int64_t token = 0;357 if (mcmc_token_get_64(rq->pr.request, &rq->pr.tok, x-1, &token) != MCMC_OK) {358 lua_pushnil(L);359 } else {360 lua_pushinteger(L, token);361 }362 return 1;363}364365int mcplib_request_ntokens(lua_State *L) {366 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");367 lua_pushinteger(L, rq->pr.tok.ntokens);368 return 1;369}370371int mcplib_request_command(lua_State *L) {372 mcp_request_t *rq = luaL_checkudata(L, -1, "mcp.request");373 lua_pushinteger(L, rq->pr.command);374 return 1;375}376377int mcplib_request_has_flag(lua_State *L) {378 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");379 size_t len = 0;380 const char *flagstr = luaL_checklstring(L, 2, &len);381 if (len != 1) {382 proxy_lua_error(L, "has_flag(): meta flag must be a single character");383 return 0;384 }385386 int status = mcmc_token_has_flag(rq->pr.request, &rq->pr.tok, flagstr[0]);387 if (status == MCMC_ERR) {388 proxy_lua_error(L, "has_flag(): invalid flag, must be A-Z,a-z");389 return 0;390 } else if (status == MCMC_OK) {391 lua_pushboolean(L, 1);392 } else {393 lua_pushboolean(L, 0);394 }395396 return 1;397}398399// req:flag_token("F") -> (bool, nil|token)400// req:flag_token("O", "Onewopauqe") -> (bool, oldtoken)401int mcplib_request_flag_token(lua_State *L) {402 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");403 size_t len = 0;404 const char *flagstr = luaL_checklstring(L, 2, &len);405 bool replace = false;406 if (len != 1) {407 proxy_lua_error(L, "has_flag(): meta flag must be a single character");408 return 0;409 }410 if (lua_isstring(L, 3)) {411 // overwriting a flag/token with the third argument.412 replace = true;413 }414415 int status = mcmc_token_has_flag(rq->pr.request, &rq->pr.tok, flagstr[0]);416 int ret = 1;417 if (status == MCMC_ERR) {418 proxy_lua_error(L, "has_flag(): invalid flag, must be A-Z,a-z");419 return 0;420 } else if (status == MCMC_OK) {421 // The flag definitely exists, but sadly we need to scan for the422 // actual flag to see if it has a token.423 lua_pushboolean(L, 1);424 int vlen = 0;425 // TODO: too much logic here since we also need to find the token426 // offset for the replace mode...427 // I think all this stuff can safely be long-term deprecated since we428 // have the mutator now so maybe I'll leave it.429 for (int x = rq->pr.keytoken+1; x < rq->pr.tok.ntokens; x++) {430 const char *s = mcmc_token_get(rq->pr.request, &rq->pr.tok, x, &vlen);431 if (s[0] == flagstr[0]) {432 if (vlen > 1) {433 // strip the flag off the token and return.434 lua_pushlstring(L, s+1, vlen-1);435 ret = 2;436 }437438 // Have something to replace the flag/token with.439 if (replace) {440 size_t newlen = 0;441 const char *newtok = lua_tolstring(L, 3, &newlen);442 if (mcp_request_render(rq, x, 0, newtok, newlen) != 0) {443 proxy_lua_error(L, "token(): request malformed after edit");444 return 0;445 }446 }447 break;448 }449 }450451 } else {452 lua_pushboolean(L, 0);453 }454455 return ret;456}457458// returns bool, int459// bool results if flag exists or not460// if int conversion fails, int is nil461int mcplib_request_flag_token_int(lua_State *L) {462 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");463 size_t len = 0;464 const char *flagstr = luaL_checklstring(L, 2, &len);465 if (len != 1) {466 proxy_lua_error(L, "has_flag(): meta flag must be a single character");467 return 0;468 }469470 int64_t val = 0;471 int status = mcmc_token_get_flag_64(rq->pr.request, &rq->pr.tok, flagstr[0], &val);472 int ret = 1;473 if (status == MCMC_NOK) {474 // explicitly token not found.475 lua_pushboolean(L, 0);476 } else {477 // token found, but conversion may have failed.478 lua_pushboolean(L, 1);479 if (status == MCMC_OK) {480 lua_pushinteger(L, val);481 ret = 2;482 }483 }484 return ret;485}486487// these functions take token as string or number488// if number, internally convert it to avoid creating garbage489static inline char _mcp_request_get_arg_flag(lua_State *L, int idx) {490 size_t len = 0;491 const char *flagstr = luaL_checklstring(L, idx, &len);492493 if (len != 1) {494 proxy_lua_error(L, "request: meta flag must be a single character");495 return 0;496 }497 if (flagstr[0] < 65 || flagstr[0] > 122) {498 proxy_lua_error(L, "request: invalid flag, must be A-Z,a-z");499 return 0;500 }501502 return flagstr[0];503}504505// *tostring must be large enough to hold a 64bit number as a string.506static inline const char * _mcp_request_check_flag_token(lua_State *L, int idx, char *tostring, size_t *tlen) {507 const char *token = NULL;508 *tlen = 0;509 if (lua_isstring(L, idx)) {510 token = lua_tolstring(L, idx, tlen);511 } else if (lua_isnumber(L, idx)) {512 int isnum = 0;513 lua_Integer n = lua_tointegerx(L, idx, &isnum);514 if (isnum) {515 char *end = itoa_64(n, tostring);516 token = tostring;517 *tlen = end - tostring;518 } else {519 proxy_lua_error(L, "request: invalid flag argument");520 return NULL;521 }522 } else if (lua_isnoneornil(L, idx)) {523 // no token, just add the flag.524 } else {525 proxy_lua_error(L, "request: invalid flag argument");526 return NULL;527 }528529 return token;530}531532// req:flag_add("F", token) -> (bool)533// if token is "example", appends "Fexample" to request534int mcplib_request_flag_add(lua_State *L) {535 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");536 char flag = _mcp_request_get_arg_flag(L, 2);537 char tostring[30];538539 if (mcmc_token_has_flag(rq->pr.request, &rq->pr.tok, flag) == MCMC_OK) {540 // fail, flag already exists.541 lua_pushboolean(L, 0);542 return 1;543 }544545 size_t tlen = 0;546 const char *token = _mcp_request_check_flag_token(L, 3, tostring, &tlen);547548 if (mcp_request_append(rq, flag, token, tlen) == 0) {549 lua_pushboolean(L, 1);550 } else {551 lua_pushboolean(L, 0);552 }553554 return 1;555}556557// req:flag_set("F", token) -> (bool) [overwrites if exists]558// if token is "example", appends "Fexample" to request559int mcplib_request_flag_set(lua_State *L) {560 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");561 char flag = _mcp_request_get_arg_flag(L, 2);562 char tostring[30];563564 int x = mcmc_token_get_flag_idx(rq->pr.request, &rq->pr.tok, flag);565 size_t tlen = 0;566 const char *token = _mcp_request_check_flag_token(L, 3, tostring, &tlen);567568 if (x > 0) {569 // TODO: do nothing if:570 // flag exists in request, without token, and we're not setting a571 // token.572 if (mcp_request_render(rq, x, flag, token, tlen) != 0) {573 lua_pushboolean(L, 0);574 return 1;575 }576 } else {577 if (mcp_request_append(rq, flag, token, tlen) != 0) {578 lua_pushboolean(L, 0);579 return 1;580 }581 }582583 lua_pushboolean(L, 1);584 return 1;585}586587// allows replacing a flag with a different flag588// req:flag_replace("F", "N", token) -> (bool)589// if token is "example", appends "Nexample" to request590int mcplib_request_flag_replace(lua_State *L) {591 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");592 char flag = _mcp_request_get_arg_flag(L, 2);593 char newflag = _mcp_request_get_arg_flag(L, 3);594 char tostring[30];595596 int x = mcmc_token_get_flag_idx(rq->pr.request, &rq->pr.tok, flag);597 size_t tlen = 0;598 const char *token = _mcp_request_check_flag_token(L, 4, tostring, &tlen);599600 if (x > 0) {601 if (mcp_request_render(rq, x, newflag, token, tlen) != 0) {602 lua_pushboolean(L, 0);603 return 1;604 }605 } else {606 if (mcp_request_append(rq, newflag, token, tlen) != 0) {607 lua_pushboolean(L, 0);608 return 1;609 }610 }611612 lua_pushboolean(L, 1);613 return 1;614}615616// req:flag_del("F") -> (bool)617// remove a flag if exists618int mcplib_request_flag_del(lua_State *L) {619 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");620 char flag = _mcp_request_get_arg_flag(L, 2);621622 int x = mcmc_token_get_flag_idx(rq->pr.request, &rq->pr.tok, flag);623624 if (x > 0) {625 if (mcp_request_render(rq, x, 0, NULL, 0) != 0) {626 lua_pushboolean(L, 0);627 return 1;628 }629 } else {630 // nothing there, didn't delete anything.631 lua_pushboolean(L, 0);632 return 1;633 }634635 lua_pushboolean(L, 1);636 return 1;637}638639// local match, token = req:match_res(res)640// checks if req has `k` or `O`. If so, checks response for `K` or `O`641// returns true, nil if matches642// returns false, res token if not match.643static inline int _mcplib_request_match_check(lua_State *L, const char *rq, int rql, const char *rs, int rsl) {644 return (rql == rsl && memcmp(rq, rs, rsl) == 0);645}646647// TODO: re-evaluate tests.648int mcplib_request_match_res(lua_State *L) {649 mcp_request_t *rq = luaL_checkudata(L, 1, "mcp.request");650 mcp_resp_t *rs = luaL_checkudata(L, 2, "mcp.response");651652 // FIXME: we reparse the buffer because it may have changed if the old API653 // was in use? Can we skip this step now or in the future?654 mcmc_resp_t reresp;655656 // requests all have keys. check for an opaque.657 int opaque_len = 0;658 const char *opaque_token = mcmc_token_get_flag(rq->pr.request, &rq->pr.tok, 'O', &opaque_len);659 if (mcmc_parse_buf(rs->buf, rs->blen, &reresp) != MCMC_OK) {660 lua_pushboolean(L, 0);661 lua_pushstring(L, "failed to parse response");662 return 2;663 }664 mcmc_tokenize_res(rs->buf, reresp.reslen, &rs->tok);665666 int matched = 0;667 int rs_len = 0;668669 if (mcmc_token_has_flag(rs->buf, &rs->tok, 'k') == MCMC_OK) {670 const char *rs_token = mcmc_token_get_flag(rs->buf, &rs->tok, 'k', &rs_len);671 matched = _mcplib_request_match_check(L, MCP_PARSER_KEY(&rq->pr), rq->pr.klen,672 rs_token, rs_len);673 if (!matched) {674 lua_pushboolean(L, 0);675 // TODO: format string to include the keys? or at least partial?676 lua_pushstring(L, "key in response does not match request key");677 return 2;678 }679 }680 if (opaque_token) {681 if (mcmc_token_has_flag(rs->buf, &rs->tok, 'O') == MCMC_OK) {682 const char *rs_token = mcmc_token_get_flag(rs->buf, &rs->tok, 'O', &rs_len);683 matched = _mcplib_request_match_check(L, opaque_token, opaque_len,684 rs_token, rs_len);685 if (!matched) {686 lua_pushboolean(L, 0);687 lua_pushstring(L, "request opaque does not match response opaque");688 return 2;689 }690 } else {691 lua_pushboolean(L, 0);692 // would be nice to include the opaque.693 // TODO: format string response here.694 lua_pushstring(L, "opaque in request but not in response");695 return 2;696 }697 }698699 lua_pushboolean(L, matched);700 lua_pushnil(L);701 return 2;702}703704void mcp_request_cleanup(LIBEVENT_THREAD *t, mcp_request_t *rq) {705 // During nread c->item is the malloc'ed buffer. not yet put into706 // rq->buf - this gets freed because we've also set c->item_malloced if707 // the connection closes before finishing nread.708 if (rq->pr.vbuf != NULL) {709 t->proxy_buffer_memory_used -= rq->pr.vlen;710 free(rq->pr.vbuf);711 // need to ensure we NULL this out now, since we can call the cleanup712 // routine independent of GC, and a later GC would double-free.713 rq->pr.vbuf = NULL;714 }715}716717int mcplib_request_gc(lua_State *L) {718 LIBEVENT_THREAD *t = PROXY_GET_THR(L);719 mcp_request_t *rq = luaL_checkudata(L, -1, "mcp.request");720 mcp_request_cleanup(t, rq);721722 return 0;723}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.
Generated by searchcode.com · powered by code_get_file + code_get_findings