Skip to content

fix: Fix crash issues.#112

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
re2zero:bugcrash
Sep 23, 2025
Merged

fix: Fix crash issues.#112
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
re2zero:bugcrash

Conversation

@re2zero
Copy link
Contributor

@re2zero re2zero commented Sep 23, 2025

  • It crashed if try to download "magnet:?xt=xxx" which may be incorrect json info.
  • It crashed if drap bt file to download, that caused by loop handle.

Log: Fix crash if magnet fink and drap file to download.

Summary by Sourcery

Fix crash scenarios when handling invalid magnet links and dragging torrent/metalink files by deferring dialog processing asynchronously and adding robust JSON validation across aria2 RPC handling and status updates.

Bug Fixes:

  • Prevent crashes when downloading invalid magnet URIs due to malformed JSON.
  • Avoid callback loops and crashes when dragging torrent or metalink files by processing them asynchronously.

Enhancements:

  • Implement queued signal-slot and QTimer-based deferred execution in CreateTaskWidget to break callback loops.
  • Add safety checks for result, bittorrent, files, uris, and announceList fields in TableDataControl to guard against malformed JSON.
  • Introduce JSON parse error handling and buffer emptiness checks in Aria2RPCInterface to ensure robust RPC response handling.

- It crashed if try to download "magnet:?xt=xxx" which may be incorrect json info.
- It crashed if drap bt file to download, that caused by loop handle.

Log: Fix crash if magnet fink and drap file to download.
@deepin-ci-robot
Copy link
Contributor

deepin pr auto review

代码审查意见

总体评价

这次代码修改主要涉及三个方面的改进:

  1. 解决了BT/Metalink文件处理中的回调循环问题
  2. 增强了JSON解析的安全性
  3. 改进了文件处理和状态更新的健壮性

详细分析

1. createtaskwidget.h 和 createtaskwidget.cpp 的改进

优点:

  • 通过引入异步处理机制(onProcessBtFileAsync和requestProcessBtFile)成功解决了回调循环问题
  • 使用Qt::QueuedConnection确保信号槽异步执行
  • 使用QTimer::singleShot(0, ...)进一步打破可能的调用链

建议改进:

  1. 在onProcessBtFileAsync函数中,建议添加文件存在性检查:
QFileInfo fileInfo(filePath);
if (!fileInfo.exists()) {
    qDebug() << "[CreateTaskWidget] Error: File does not exist:" << filePath;
    close();
    return;
}
  1. 考虑添加错误处理机制,当BtInfoDialog创建失败时的处理:
if (!dialog) {
    qDebug() << "[CreateTaskWidget] Error: Failed to create BtInfoDialog";
    close();
    return;
}

2. aria2rpcinterface.cpp 的改进

优点:

  • 增加了多层安全检查,包括空响应、JSON解析、对象有效性等
  • 提供了详细的错误日志,便于调试

建议改进:

  1. 考虑添加重试机制,对于临时性网络错误:
if (code == 503 || code == 504) {  // 服务不可用或网关超时
    qDebug() << "[Aria2RPC] Retrying request due to temporary error:" << code;
    // 实现重试逻辑
    return;
}
  1. 对于JSON解析,可以添加更详细的错误恢复机制:
if (parseError.error != QJsonParseError::NoError) {
    // 尝试清理并重新解析
    QByteArray cleanedBuf = buf.trimmed();
    doc = QJsonDocument::fromJson(cleanedBuf, &parseError);
    if (parseError.error != QJsonParseError::NoError) {
        qDebug() << "[Aria2RPC] Error: JSON parse failed after cleanup";
        return;
    }
}

3. tabledatacontrol.cpp 的改进

优点:

  • 增加了多层安全检查,防止空指针和无效访问
  • 改进了JSON数据访问的安全性

建议改进:

  1. 对于数值转换,建议添加范围检查:
bool ok;
long value = str.toLong(&ok);
if (!ok || value < 0) {
    qDebug() << "Error: Invalid numeric value:" << str;
    return false;
}
  1. 对于数组访问,建议添加边界检查:
if (index < 0 || index >= array.size()) {
    qDebug() << "Error: Array index out of bounds:" << index;
    return false;
}

安全性建议

  1. 文件路径处理:

    • 添加路径规范化处理,防止路径遍历攻击
    • 验证文件扩展名,防止恶意文件上传
  2. 输入验证:

    • 对所有外部输入进行严格验证
    • 使用白名单而非黑名单方式验证文件类型
  3. 错误处理:

    • 避免在错误信息中暴露敏感信息
    • 实现统一的错误处理机制

性能建议

  1. 对于频繁调用的函数(如aria2MethodStatusChanged),考虑缓存常用对象
  2. 对于大型JSON处理,考虑使用流式解析而非一次性加载
  3. 优化信号槽连接,避免不必要的信号发射

总结

这次代码修改在解决回调循环问题和增强代码健壮性方面做得很好。建议在后续开发中:

  1. 继续加强错误处理和输入验证
  2. 添加更多的边界条件检查
  3. 考虑实现更完善的错误恢复机制
  4. 添加适当的性能优化措施

这些改进将有助于提高代码的可靠性、安全性和性能。

@sourcery-ai
Copy link

sourcery-ai bot commented Sep 23, 2025

Reviewer's Guide

This PR refactors CreateTaskWidget to break callback loops by deferring BT/Metalink file dialogs via a new signal-slot and QTimer-based async path, and adds defensive JSON parsing and validation in both TableDataControl and Aria2RPCInterface to avoid crashes on malformed or missing data.

File-Level Changes

Change Details Files
Introduced async processing to prevent callback loops when handling BT/Metalink files
  • Declared requestProcessBtFile signal and onProcessBtFileAsync slot in the header
  • Connected requestProcessBtFile to onProcessBtFileAsync using Qt::QueuedConnection
  • Replaced direct BtInfoDialog::exec calls in onFileDialogOpen, onMLFileDialogOpen, and dropEvent with emits of requestProcessBtFile
  • Implemented onProcessBtFileAsync to call the dialog via QTimer::singleShot and emit download signals after acceptance
createtaskwidget.cpp
createtaskwidget.h
Added safety checks around JSON fields in TableDataControl status updates
  • Verified that result, bittorrent, files, and uris fields exist and have the expected types before use
  • Logged errors and returned false early if any JSON element is missing or malformed
  • Guarded announceList access to prevent null or non-array values
tabledatacontrol.cpp
Enhanced JSON parsing and validation in Aria2RPCInterface::rpcRequestReply
  • Checked for empty network reply buffers before parsing
  • Captured and handled QJsonParseError to abort on invalid JSON
  • Ensured parsed QJsonDocument is an object and non-empty before emitting success
aria2rpcinterface.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • You have introduced many repetitive JSON safety checks in TableDataControl and Aria2RPCInterface; consider extracting common validation helpers to reduce boilerplate and improve maintainability.
  • Using QTimer::singleShot to defer BT/Metalink dialog creation may introduce race conditions if CreateTaskWidget is destroyed before execution; ensure the widget’s lifetime is correctly managed or guard against invalid this pointers.
  • For Aria2RPCInterface, instead of silently returning on JSON parse errors, consider propagating an error signal or status so the UI can provide feedback to the user.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- You have introduced many repetitive JSON safety checks in TableDataControl and Aria2RPCInterface; consider extracting common validation helpers to reduce boilerplate and improve maintainability.
- Using QTimer::singleShot to defer BT/Metalink dialog creation may introduce race conditions if CreateTaskWidget is destroyed before execution; ensure the widget’s lifetime is correctly managed or guard against invalid `this` pointers.
- For Aria2RPCInterface, instead of silently returning on JSON parse errors, consider propagating an error signal or status so the UI can provide feedback to the user.

## Individual Comments

### Comment 1
<location> `src/src/ui/mainFrame/tabledatacontrol.cpp:286-293` </location>
<code_context>
+    
     if (files.size() == 1) {
-        filePath = files[0].toObject().value("path").toString();
+        filePath = firstFile.value("path").toString();
     } else {
-        QString path = files[0].toObject().value("path").toString();
</code_context>

<issue_to_address>
**suggestion:** No fallback if 'path' field is missing or not a string.

Downstream code may fail if filePath is empty. Please add validation or a fallback for missing or invalid 'path' values.

```suggestion
    QString pathValue;
    if (firstFile.contains("path") && firstFile.value("path").isString()) {
        pathValue = firstFile.value("path").toString();
    } else {
        qDebug() << "Warning: 'path' field missing or not a string in file object, using fallback";
        pathValue = QString(); // fallback: empty string, or set to a default if desired
    }

    if (files.size() == 1) {
        filePath = pathValue;
    } else {
        QString path2 = result.value("dir").toString();
        if (!pathValue.isEmpty()) {
            filePath = path2 + "/" + pathValue.split('/').at(path2.split('/').count());
        } else {
            filePath = path2; // fallback: just use dir if path is missing
        }
        //filePath = path.left(path.count() - path.split('/').last().count() - 1);
    }
```
</issue_to_address>

### Comment 2
<location> `src/src/ui/mainFrame/tabledatacontrol.cpp:250` </location>
<code_context>
-    QJsonObject result = json.value("result").toObject();
-    QJsonObject bittorrent = result.value("bittorrent").toObject();
+    
+    // Minimal safety check: ensure result field exists and is an object
+    QJsonValue resultValue = json.value("result");
+    if (!resultValue.isObject()) {
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring repetitive JSON type checks into small helper functions for cleaner and more readable code.

You can collapse all of these repetitive `isObject/isArray` guard‐clauses into three tiny helpers, then your code reads almost like pseudo-code:

```cpp
// somewhere in a shared/util header
static inline QJsonObject asObj (const QJsonValue& v)   { return v.isObject() ? v.toObject() : QJsonObject(); }
static inline QJsonArray  asArr (const QJsonValue& v)   { return v.isArray()  ? v.toArray()  : QJsonArray();  }
static inline QString     asStr (const QJsonValue& v)   { return v.isString() ? v.toString() : QString();  }
```

Then your `aria2MethodStatusChanged` can shrink to:

```cpp
bool TableDataControl::aria2MethodStatusChanged(QJsonObject &json, int row, QString &searchContent) {
    auto result    = asObj(json["result"]);
    if (result.isEmpty()) return false;

    auto files     = asArr(result["files"]);
    if (files.isEmpty()) return false;

    auto firstFile = asObj(files.first());
    QString filePath;
    if (files.size() == 1) {
        filePath = asStr(firstFile["path"]);
    } else {
        auto dir = asStr(result["dir"]);
        auto relative = asStr(firstFile["path"]);
        filePath = dir + "/" + relative.split('/').at(dir.split('/').count());
    }

    auto uris      = asArr(firstFile["uris"]);
    QString fileUri = !uris.isEmpty()
                     ? asStr(asObj(uris.first())["uri"])
                     : QString();

    // ...
    auto bittorrent = asObj(result["bittorrent"]);
    data->announceList = asArr(bittorrent["announceList"]).size();

    // rest of logic unchanged

}
```

Do the same in `aria2GetGlobalStatus` (and any other JSON access) so you only call `asObj`, `asArr`, and `asStr` instead of littering `isObject`/`isArray` every time.
</issue_to_address>

### Comment 3
<location> `src/src/aria2/aria2rpcinterface.cpp:583` </location>
<code_context>
     QByteArray buf = reply->readAll(); //获取信息
-    QJsonDocument doc = QJsonDocument::fromJson(buf); //转换为json格式
+    
+    // Safety check: validate JSON parsing
+    if (buf.isEmpty()) {
+        qDebug() << "[Aria2RPC] Error: Empty response buffer for method:" << method;
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the JSON parsing and validation logic into a reusable helper function to simplify the main handler code.

Here’s a small focused refactoring that pulls all of that JSON‐parsing/validation logic out into one helper.  This keeps your `sendMessage` callback almost as short as it was before, but still preserves all of your new safety checks.

In some common header (e.g. `RpcUtils.h`):

```cpp
#pragma once
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>

inline bool parseAndValidateJsonObject(
    const QByteArray &buf,
    const QString &method,
    QJsonObject &outObj)
{
    if (buf.isEmpty()) {
        qDebug() << "[Aria2RPC] Error: Empty response buffer for method:" << method;
        return false;
    }
    QJsonParseError parseError;
    QJsonDocument doc = QJsonDocument::fromJson(buf, &parseError);
    if (parseError.error != QJsonParseError::NoError) {
        qDebug() << "[Aria2RPC] Error: JSON parse failed for method:" 
                 << method << "Error:" << parseError.errorString()
                 << "Offset:" << parseError.offset;
        return false;
    }
    if (!doc.isObject()) {
        qDebug() << "[Aria2RPC] Error: JSON document is not an object for method:" << method;
        return false;
    }
    outObj = doc.object();
    if (outObj.isEmpty()) {
        qDebug() << "[Aria2RPC] Error: JSON object is empty for method:" << method;
        return false;
    }
    return true;
}
```

Then your handler becomes:

```cpp
QByteArray buf = reply->readAll();
QJsonObject obj;
if (!parseAndValidateJsonObject(buf, method, obj)) {
    reply->deleteLater();
    return;
}

int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (code == 200) {
    qDebug() << "[Aria2RPC] sendMessage, method =" << method << ", code = 200";
    emit RPCSuccess(method, obj);
} else {
    qDebug() << "[Aria2RPC] sendMessage, method =" << method << ", code =" << code;
    emit RPCError(method, id, code, obj);
}
reply->deleteLater();
```

This removes the inline duplication and nesting while keeping every one of your safety checks.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 286 to 293
if (files.size() == 1) {
filePath = files[0].toObject().value("path").toString();
filePath = firstFile.value("path").toString();
} else {
QString path = files[0].toObject().value("path").toString();
QString path = firstFile.value("path").toString();
QString path2 = result.value("dir").toString();
filePath = path2 + "/" + path.split('/').at(path2.split('/').count());
//filePath = path.left(path.count() - path.split('/').last().count() - 1);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: No fallback if 'path' field is missing or not a string.

Downstream code may fail if filePath is empty. Please add validation or a fallback for missing or invalid 'path' values.

Suggested change
if (files.size() == 1) {
filePath = files[0].toObject().value("path").toString();
filePath = firstFile.value("path").toString();
} else {
QString path = files[0].toObject().value("path").toString();
QString path = firstFile.value("path").toString();
QString path2 = result.value("dir").toString();
filePath = path2 + "/" + path.split('/').at(path2.split('/').count());
//filePath = path.left(path.count() - path.split('/').last().count() - 1);
}
QString pathValue;
if (firstFile.contains("path") && firstFile.value("path").isString()) {
pathValue = firstFile.value("path").toString();
} else {
qDebug() << "Warning: 'path' field missing or not a string in file object, using fallback";
pathValue = QString(); // fallback: empty string, or set to a default if desired
}
if (files.size() == 1) {
filePath = pathValue;
} else {
QString path2 = result.value("dir").toString();
if (!pathValue.isEmpty()) {
filePath = path2 + "/" + pathValue.split('/').at(path2.split('/').count());
} else {
filePath = path2; // fallback: just use dir if path is missing
}
//filePath = path.left(path.count() - path.split('/').last().count() - 1);
}

@deepin-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: lzwind, re2zero

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@re2zero
Copy link
Contributor Author

re2zero commented Sep 23, 2025

/merge

@deepin-bot deepin-bot bot merged commit 9c966d7 into linuxdeepin:master Sep 23, 2025
18 checks passed
@re2zero re2zero deleted the bugcrash branch September 23, 2025 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants