diff --git a/.github/workflows/upstream-commit-check.yml b/.github/workflows/upstream-commit-check.yml new file mode 100644 index 0000000000000..549b502728d9f --- /dev/null +++ b/.github/workflows/upstream-commit-check.yml @@ -0,0 +1,170 @@ +name: Check Upstream Linux Kernel Commits + +on: + pull_request: + branches: + - '**' + - '!mainline' + +jobs: + check_upstream_fixes_tag: + runs-on: ubuntu-latest + + steps: + - name: Install jq + run: | + sudo apt-get update && sudo apt-get install -y jq + + - name: Clone Linux Kernel + run: | + echo "Cloning Linux kernel repository (bare clone, single branch, no blobs) into 'linux' directory..." + git clone --bare --filter=blob:none --single-branch https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git linux + echo "Cloning complete." + + - name: Get PR commit SHAs via GitHub API + id: pr_commits + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Required for GitHub API authentication + run: | + PR_COMMITS_JSON=$(curl -sS -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token $GITHUB_TOKEN" \ + "${{ github.event.pull_request.commits_url }}?per_page=100") + + ERROR_MESSAGE=$(echo "$PR_COMMITS_JSON" | jq -r 'if type == "object" and .message then .message else null end') + if [ "$ERROR_MESSAGE" != "null" ] && [ -n "$ERROR_MESSAGE" ]; then + echo "ERROR: Failed to retrieve PR commits from GitHub API: $ERROR_MESSAGE" + echo "API Response snippet: $(echo "$PR_COMMITS_JSON" | head -n 5)" + exit 1 + fi + + COMMITS=$(echo "$PR_COMMITS_JSON" | jq -r 'map(.sha) | join(" ")') + + if [ -z "$COMMITS" ]; then + echo "No commits found in this Pull Request via GitHub API. This might indicate an issue with the PR or API." + exit 1 + fi + + echo "PR_COMMITS=${COMMITS}" >> "$GITHUB_OUTPUT" + + - name: Check each PR commit for Linux upstream hash and related Fixes tag + id: check_results + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + LINUX_KERNEL_REPO_OWNER: torvalds + LINUX_KERNEL_REPO_NAME: linux + run: | + PR_COMMENT_BODY_ACCUMULATOR="" + + # Get the current HEAD commit SHA for the Linux kernel master branch from the local clone + echo "Getting current HEAD of '${LINUX_KERNEL_REPO_OWNER}/${LINUX_KERNEL_REPO_NAME}' master branch from local bare clone..." + + HEAD_COMMIT_SHA=$(git -C linux rev-parse master) + + if [ -z "$HEAD_COMMIT_SHA" ]; then + echo "ERROR: Could not retrieve HEAD commit for Linux kernel master from local clone." + exit 1 + fi + + echo "Linux kernel master HEAD: ${HEAD_COMMIT_SHA}" + + + # Loop through each commit SHA identified in the current PR + for PR_COMMIT_SHA in ${{ steps.pr_commits.outputs.PR_COMMITS }}; do + echo "--- Checking PR commit: ${PR_COMMIT_SHA} ---" + + # --- Fetch the full commit message of the PR commit via GitHub API --- + PR_COMMIT_DETAILS_JSON=$(curl -sS -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token $GITHUB_TOKEN" \ + "https://api.github.com/repos/${{ github.repository_owner }}/${{ github.event.repository.name }}/commits/${PR_COMMIT_SHA}") + + ERROR_MESSAGE=$(echo "$PR_COMMIT_DETAILS_JSON" | jq -r 'if type == "object" and .message then .message else null end') + if [ "$ERROR_MESSAGE" != "null" ] && [ -n "$ERROR_MESSAGE" ]; then + echo "ERROR: Could not retrieve commit message for PR commit ${PR_COMMIT_SHA} from GitHub API: ${ERROR_MESSAGE}" + echo "API Response snippet: $(echo "$PR_COMMIT_DETAILS_JSON" | head -n 5)" + exit 1 + fi + + PR_COMMIT_MESSAGE=$(echo "$PR_COMMIT_DETAILS_JSON" | jq -r '.commit.message') + # Extract the subject (first line) of the PR commit message + PR_COMMIT_SUBJECT=$(echo "$PR_COMMIT_MESSAGE" | head -n 1) + + # Extract the upstream Linux commit hash from the PR commit message. + UPSTREAM_LINUX_HASH=$(echo "$PR_COMMIT_MESSAGE" | grep -Eo "^commit [0-9a-f]{40}$" | awk '{print $2}') + + if [ -z "$UPSTREAM_LINUX_HASH" ]; then + echo "No 'commit ' line found in PR commit ${PR_COMMIT_SHA}. Skipping upstream check for this commit." + continue # Skip to next PR commit, no comment for this scenario + fi + + echo "Found upstream Linux hash to check for fixes: ${UPSTREAM_LINUX_HASH}" + + # --- Check if the upstream hash exists in the local cloned Linux kernel repo --- + if ! git -C linux cat-file -e "$UPSTREAM_LINUX_HASH"; then + printf -v PR_COMMENT_BODY_ACCUMULATOR "%s- **PR commit \`%s\` - %s**: References upstream commit \`%s\` which was **NOT found** in the Linux kernel repository. Please verify the hash.\n" \ + "$PR_COMMENT_BODY_ACCUMULATOR" "$PR_COMMIT_SHA" "$PR_COMMIT_SUBJECT" "$UPSTREAM_LINUX_HASH" + continue # Skip to next PR commit, but added message to comment + fi + + # --- Search for "Fixes:" tag in upstream Linux kernel using git log --- + # Extract the first 12 characters for the short SHA, commonly used in Fixes: tags in Linux kernel. + UPSTREAM_LINUX_HASH_SHORT=$(echo "$UPSTREAM_LINUX_HASH" | cut -c 1-12) + + echo "Searching for upstream commits on 'master' branch in range ${UPSTREAM_LINUX_HASH}..${HEAD_COMMIT_SHA} that fix ${UPSTREAM_LINUX_HASH} using 'git log --grep=\"Fixes: ${UPSTREAM_LINUX_HASH_SHORT}\"'..." + + # Construct the grep pattern using the SHORT SHA for search. + GREP_PATTERN="Fixes: ${UPSTREAM_LINUX_HASH_SHORT}" + + # Use git log to find commits that mention the short SHA in their "Fixes:" line, + # in the range from the specific commit up to HEAD. + # --pretty=format:"%h %s" to get short SHA and subject. + # --regexp-ignore-case for case-insensitive grep. + # We explicitly exclude the UPSTREAM_LINUX_HASH itself from the result range, + # as we are looking for *subsequent* fixes. + GIT_LOG_FIXES_OUTPUT=$(git -C linux log master \ + "${UPSTREAM_LINUX_HASH}..${HEAD_COMMIT_SHA}" \ + --grep="${GREP_PATTERN}" \ + --pretty=format:"%h %s" \ + --regexp-ignore-case) + + if [ -n "$GIT_LOG_FIXES_OUTPUT" ]; then + printf -v PR_COMMENT_BODY_ACCUMULATOR "%s- **PR commit \`%s\` - %s**: References upstream commit \`%s\` which has the following fixes in the Linux kernel log:\n" \ + "$PR_COMMENT_BODY_ACCUMULATOR" "$PR_COMMIT_SHA" "$PR_COMMIT_SUBJECT" "$UPSTREAM_LINUX_HASH" + # Add a markdown code block for the git log output + printf -v PR_COMMENT_BODY_ACCUMULATOR "%s\`\`\`\n%s\n\`\`\`\n" \ + "$PR_COMMENT_BODY_ACCUMULATOR" "$GIT_LOG_FIXES_OUTPUT" + else + echo "No upstream fixes found for ${UPSTREAM_LINUX_HASH} in the specified range." + # No comment added to PR_COMMENT_BODY_ACCUMULATOR for this scenario + fi + echo "" # Newline in action logs for separation + done # End of for PR_COMMIT_SHA loop + + # Set the output variable `PR_COMMENT_BODY` using EOF to preserve newlines for the PR comment. + # If no relevant messages were accumulated, this will be an empty string (after trim in JS). + echo "PR_COMMENT_BODY<> "$GITHUB_OUTPUT" + echo "$PR_COMMENT_BODY_ACCUMULATOR" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + + - name: Post PR comment with results # Step 3: Post the collected results as a comment on the PR + uses: actions/github-script@v7 + env: + # Pass the multi-line comment body as an environment variable to the JavaScript script. + COMMENT_BODY: ${{ steps.check_results.outputs.PR_COMMENT_BODY }} + with: + script: | + // Access the comment body directly from the environment variable and trim any whitespace + const commentBody = process.env.COMMENT_BODY.trim(); + + // Only post a comment if there is actual content + if (commentBody) { + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }); + console.log("Posted a PR comment with the results."); + } else { + console.log("No relevant upstream fixes information to post as a PR comment."); + } diff --git a/Makefile b/Makefile index 6a53e15f15c04..c0d76e7db8270 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 + + + + VERSION = 4 PATCHLEVEL = 18 SUBLEVEL = 0 diff --git a/drivers/gpu/drm/qxl/qxl_drv.h b/drivers/gpu/drm/qxl/qxl_drv.h index dd6abee55f56c..65349a115ef3f 100644 --- a/drivers/gpu/drm/qxl/qxl_drv.h +++ b/drivers/gpu/drm/qxl/qxl_drv.h @@ -318,7 +318,7 @@ int qxl_gem_object_create_with_handle(struct qxl_device *qdev, u32 domain, size_t size, struct qxl_surface *surf, - struct qxl_bo **qobj, + struct drm_gem_object **gobj, uint32_t *handle); void qxl_gem_object_free(struct drm_gem_object *gobj); int qxl_gem_object_open(struct drm_gem_object *obj, struct drm_file *file_priv); diff --git a/drivers/gpu/drm/qxl/qxl_dumb.c b/drivers/gpu/drm/qxl/qxl_dumb.c index d636ba6854513..17df5c7ccf691 100644 --- a/drivers/gpu/drm/qxl/qxl_dumb.c +++ b/drivers/gpu/drm/qxl/qxl_dumb.c @@ -34,6 +34,7 @@ int qxl_mode_dumb_create(struct drm_file *file_priv, { struct qxl_device *qdev = to_qxl(dev); struct qxl_bo *qobj; + struct drm_gem_object *gobj; uint32_t handle; int r; struct qxl_surface surf; @@ -62,11 +63,13 @@ int qxl_mode_dumb_create(struct drm_file *file_priv, r = qxl_gem_object_create_with_handle(qdev, file_priv, QXL_GEM_DOMAIN_CPU, - args->size, &surf, &qobj, + args->size, &surf, &gobj, &handle); if (r) return r; + qobj = gem_to_qxl_bo(gobj); qobj->is_dumb = true; + drm_gem_object_put(gobj); args->pitch = pitch; args->handle = handle; return 0; diff --git a/drivers/gpu/drm/qxl/qxl_gem.c b/drivers/gpu/drm/qxl/qxl_gem.c index a08da0bd9098b..fc5e3763c3595 100644 --- a/drivers/gpu/drm/qxl/qxl_gem.c +++ b/drivers/gpu/drm/qxl/qxl_gem.c @@ -72,32 +72,41 @@ int qxl_gem_object_create(struct qxl_device *qdev, int size, return 0; } +/* + * If the caller passed a valid gobj pointer, it is responsible to call + * drm_gem_object_put() when it no longer needs to acess the object. + * + * If gobj is NULL, it is handled internally. + */ int qxl_gem_object_create_with_handle(struct qxl_device *qdev, struct drm_file *file_priv, u32 domain, size_t size, struct qxl_surface *surf, - struct qxl_bo **qobj, + struct drm_gem_object **gobj, uint32_t *handle) { - struct drm_gem_object *gobj; int r; + struct drm_gem_object *local_gobj; - BUG_ON(!qobj); BUG_ON(!handle); r = qxl_gem_object_create(qdev, size, 0, domain, false, false, surf, - &gobj); + &local_gobj); if (r) return -ENOMEM; - r = drm_gem_handle_create(file_priv, gobj, handle); + r = drm_gem_handle_create(file_priv, local_gobj, handle); if (r) return r; - /* drop reference from allocate - handle holds it now */ - *qobj = gem_to_qxl_bo(gobj); - drm_gem_object_put(gobj); + + if (gobj) + *gobj = local_gobj; + else + /* drop reference from allocate - handle holds it now */ + drm_gem_object_put(local_gobj); + return 0; } diff --git a/drivers/gpu/drm/qxl/qxl_ioctl.c b/drivers/gpu/drm/qxl/qxl_ioctl.c index 38aabcbe22382..4066499ca79e0 100644 --- a/drivers/gpu/drm/qxl/qxl_ioctl.c +++ b/drivers/gpu/drm/qxl/qxl_ioctl.c @@ -39,7 +39,6 @@ static int qxl_alloc_ioctl(struct drm_device *dev, void *data, struct qxl_device *qdev = to_qxl(dev); struct drm_qxl_alloc *qxl_alloc = data; int ret; - struct qxl_bo *qobj; uint32_t handle; u32 domain = QXL_GEM_DOMAIN_VRAM; @@ -51,7 +50,7 @@ static int qxl_alloc_ioctl(struct drm_device *dev, void *data, domain, qxl_alloc->size, NULL, - &qobj, &handle); + NULL, &handle); if (ret) { DRM_ERROR("%s: failed to create gem ret=%d\n", __func__, ret); @@ -393,7 +392,6 @@ static int qxl_alloc_surf_ioctl(struct drm_device *dev, void *data, { struct qxl_device *qdev = to_qxl(dev); struct drm_qxl_alloc_surf *param = data; - struct qxl_bo *qobj; int handle; int ret; int size, actual_stride; @@ -413,7 +411,7 @@ static int qxl_alloc_surf_ioctl(struct drm_device *dev, void *data, QXL_GEM_DOMAIN_SURFACE, size, &surf, - &qobj, &handle); + NULL, &handle); if (ret) { DRM_ERROR("%s: failed to create gem ret=%d\n", __func__, ret); diff --git a/drivers/isdn/mISDN/l1oip.h b/drivers/isdn/mISDN/l1oip.h index 7ea10db20e3a6..48133d0228120 100644 --- a/drivers/isdn/mISDN/l1oip.h +++ b/drivers/isdn/mISDN/l1oip.h @@ -59,6 +59,7 @@ struct l1oip { int bundle; /* bundle channels in one frm */ int codec; /* codec to use for transmis. */ int limit; /* limit number of bchannels */ + bool shutdown; /* if card is released */ /* timer */ struct timer_list keep_tl; diff --git a/drivers/isdn/mISDN/l1oip_core.c b/drivers/isdn/mISDN/l1oip_core.c index 072bb5e36c184..be6eb29410764 100644 --- a/drivers/isdn/mISDN/l1oip_core.c +++ b/drivers/isdn/mISDN/l1oip_core.c @@ -289,7 +289,7 @@ l1oip_socket_send(struct l1oip *hc, u8 localcodec, u8 channel, u32 chanmask, p = frame; /* restart timer */ - if (time_before(hc->keep_tl.expires, jiffies + 5 * HZ)) + if (time_before(hc->keep_tl.expires, jiffies + 5 * HZ) && !hc->shutdown) mod_timer(&hc->keep_tl, jiffies + L1OIP_KEEPALIVE * HZ); else hc->keep_tl.expires = jiffies + L1OIP_KEEPALIVE * HZ; @@ -615,7 +615,9 @@ l1oip_socket_parse(struct l1oip *hc, struct sockaddr_in *sin, u8 *buf, int len) goto multiframe; /* restart timer */ - if (time_before(hc->timeout_tl.expires, jiffies + 5 * HZ) || !hc->timeout_on) { + if ((time_before(hc->timeout_tl.expires, jiffies + 5 * HZ) || + !hc->timeout_on) && + !hc->shutdown) { hc->timeout_on = 1; mod_timer(&hc->timeout_tl, jiffies + L1OIP_TIMEOUT * HZ); } else /* only adjust timer */ @@ -1246,11 +1248,10 @@ release_card(struct l1oip *hc) { int ch; - if (timer_pending(&hc->keep_tl)) - del_timer(&hc->keep_tl); + hc->shutdown = true; - if (timer_pending(&hc->timeout_tl)) - del_timer(&hc->timeout_tl); + del_timer_sync(&hc->keep_tl); + del_timer_sync(&hc->timeout_tl); cancel_work_sync(&hc->workq); diff --git a/drivers/media/usb/dvb-usb/technisat-usb2.c b/drivers/media/usb/dvb-usb/technisat-usb2.c index 1c00b255089a6..f172120db2aa7 100644 --- a/drivers/media/usb/dvb-usb/technisat-usb2.c +++ b/drivers/media/usb/dvb-usb/technisat-usb2.c @@ -608,10 +608,9 @@ static int technisat_usb2_frontend_attach(struct dvb_usb_adapter *a) static int technisat_usb2_get_ir(struct dvb_usb_device *d) { struct technisat_usb2_state *state = d->priv; - u8 *buf = state->buf; - u8 *b; - int ret; struct ir_raw_event ev; + u8 *buf = state->buf; + int i, ret; buf[0] = GET_IR_DATA_VENDOR_REQUEST; buf[1] = 0x08; @@ -647,26 +646,25 @@ static int technisat_usb2_get_ir(struct dvb_usb_device *d) return 0; /* no key pressed */ /* decoding */ - b = buf+1; #if 0 deb_rc("RC: %d ", ret); - debug_dump(b, ret, deb_rc); + debug_dump(buf + 1, ret, deb_rc); #endif ev.pulse = 0; - while (1) { - ev.pulse = !ev.pulse; - ev.duration = (*b * FIRMWARE_CLOCK_DIVISOR * FIRMWARE_CLOCK_TICK) / 1000; - ir_raw_event_store(d->rc_dev, &ev); - - b++; - if (*b == 0xff) { + for (i = 1; i < ARRAY_SIZE(state->buf); i++) { + if (buf[i] == 0xff) { ev.pulse = 0; ev.duration = 888888*2; ir_raw_event_store(d->rc_dev, &ev); break; } + + ev.pulse = !ev.pulse; + ev.duration = (buf[i] * FIRMWARE_CLOCK_DIVISOR * + FIRMWARE_CLOCK_TICK) / 1000; + ir_raw_event_store(d->rc_dev, &ev); } ir_raw_event_handle(d->rc_dev); diff --git a/net/atm/ioctl.c b/net/atm/ioctl.c index 2ff0e5e470e3d..38f7f164e4848 100644 --- a/net/atm/ioctl.c +++ b/net/atm/ioctl.c @@ -71,14 +71,17 @@ static int do_vcc_ioctl(struct socket *sock, unsigned int cmd, case SIOCINQ: { struct sk_buff *skb; + int amount; if (sock->state != SS_CONNECTED) { error = -EINVAL; goto done; } + spin_lock_irq(&sk->sk_receive_queue.lock); skb = skb_peek(&sk->sk_receive_queue); - error = put_user(skb ? skb->len : 0, - (int __user *)argp) ? -EFAULT : 0; + amount = skb ? skb->len : 0; + spin_unlock_irq(&sk->sk_receive_queue.lock); + error = put_user(amount, (int __user *)argp) ? -EFAULT : 0; goto done; } case SIOCGSTAMP: /* borrowed from IP */ diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c index 5176c82a2104c..6a7db70c41a27 100644 --- a/net/bluetooth/af_bluetooth.c +++ b/net/bluetooth/af_bluetooth.c @@ -263,11 +263,14 @@ int bt_sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, if (flags & MSG_OOB) return -EOPNOTSUPP; + lock_sock(sk); + skb = skb_recv_datagram(sk, flags, noblock, &err); if (!skb) { if (sk->sk_shutdown & RCV_SHUTDOWN) - return 0; + err = 0; + release_sock(sk); return err; } @@ -293,6 +296,8 @@ int bt_sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, skb_free_datagram(sk, skb); + release_sock(sk); + if (flags & MSG_TRUNC) copied = skblen; diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 9239137e61c07..5c1578cd40849 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -135,13 +135,11 @@ static void hci_conn_cleanup(struct hci_conn *conn) hdev->notify(hdev, HCI_NOTIFY_CONN_DEL); } - hci_conn_del_sysfs(conn); - debugfs_remove_recursive(conn->debugfs); - hci_dev_put(hdev); + hci_conn_del_sysfs(conn); - hci_conn_put(conn); + hci_dev_put(hdev); } static void le_scan_cleanup(struct work_struct *work) diff --git a/net/bluetooth/hci_sysfs.c b/net/bluetooth/hci_sysfs.c index b69d88b88d2e4..21ce505da8df4 100644 --- a/net/bluetooth/hci_sysfs.c +++ b/net/bluetooth/hci_sysfs.c @@ -33,7 +33,7 @@ void hci_conn_init_sysfs(struct hci_conn *conn) { struct hci_dev *hdev = conn->hdev; - BT_DBG("conn %p", conn); + bt_dev_dbg(hdev, "conn %p", conn); conn->dev.type = &bt_link; conn->dev.class = bt_class; @@ -46,24 +46,27 @@ void hci_conn_add_sysfs(struct hci_conn *conn) { struct hci_dev *hdev = conn->hdev; - BT_DBG("conn %p", conn); + bt_dev_dbg(hdev, "conn %p", conn); dev_set_name(&conn->dev, "%s:%d", hdev->name, conn->handle); - if (device_add(&conn->dev) < 0) { + if (device_add(&conn->dev) < 0) bt_dev_err(hdev, "failed to register connection device"); - return; - } - - hci_dev_hold(hdev); } void hci_conn_del_sysfs(struct hci_conn *conn) { struct hci_dev *hdev = conn->hdev; - if (!device_is_registered(&conn->dev)) + bt_dev_dbg(hdev, "conn %p", conn); + + if (!device_is_registered(&conn->dev)) { + /* If device_add() has *not* succeeded, use *only* put_device() + * to drop the reference count. + */ + put_device(&conn->dev); return; + } while (1) { struct device *dev; @@ -75,9 +78,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn) put_device(dev); } - device_del(&conn->dev); - - hci_dev_put(hdev); + device_unregister(&conn->dev); } static void bt_host_release(struct device *dev) diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index 1c2fdc26474d4..93924863cb454 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -222,8 +222,10 @@ static void igmp_start_timer(struct ip_mc_list *im, int max_delay) int tv = prandom_u32() % max_delay; im->tm_running = 1; - if (!mod_timer(&im->timer, jiffies+tv+2)) - refcount_inc(&im->refcnt); + if (refcount_inc_not_zero(&im->refcnt)) { + if (mod_timer(&im->timer, jiffies + tv + 2)) + ip_ma_put(im); + } } static void igmp_gq_start_timer(struct in_device *in_dev) diff --git a/net/netfilter/nft_dynset.c b/net/netfilter/nft_dynset.c index f407b3e9c178d..ae61505d4c41e 100644 --- a/net/netfilter/nft_dynset.c +++ b/net/netfilter/nft_dynset.c @@ -274,10 +274,15 @@ static int nft_dynset_init(const struct nft_ctx *ctx, priv->expr_array[i] = dynset_expr; priv->num_exprs++; - if (set->num_exprs && - dynset_expr->ops != set->exprs[i]->ops) { - err = -EOPNOTSUPP; - goto err_expr_free; + if (set->num_exprs) { + if (i >= set->num_exprs) { + err = -EINVAL; + goto err_expr_free; + } + if (dynset_expr->ops != set->exprs[i]->ops) { + err = -EOPNOTSUPP; + goto err_expr_free; + } } i++; }