You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Clang静态分析器Checker:引用类型get/set函数断言崩溃排查

Clang静态分析器Checker处理引用返回函数时的断言崩溃问题

问题背景

需要实现一个Clang静态分析器Checker,处理一对函数:

void set(const int& value);
const int& get();

实际逻辑是set将传入引用指向的值保存到内部int变量,get返回该变量的引用。原Checker实现代码如下:

#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"

using namespace clang;
using namespace ento;

namespace {

class checkerTest : public Checker<eval::Call> {

  bool handleSet(CheckerContext &C, const CallEvent &Call) const;
  bool handleGet(CheckerContext &C, const CallEvent &Call) const;

public:
  bool evalCall(const CallEvent &Call, CheckerContext &C) const;

  using FnHandler = bool (checkerTest::*)(CheckerContext &, const CallEvent &Call) const;
  CallDescriptionMap<FnHandler> Functions = {
      {{{"set"}, 1}, &checkerTest::handleSet},
      {{{"get"}, 0}, &checkerTest::handleGet},
  };
};

} // namespace

SVal g_value;

bool checkerTest::handleSet(CheckerContext &C, const CallEvent &Call) const {
  SVal location = Call.getArgSVal(0);
  QualType LoadTy = Call.getArgExpr(0)->getType();
  ProgramStateRef State = C.getState();
  SVal Value = State->getSVal(location.castAs<Loc>(), LoadTy);
  C.addTransition(State);
  g_value = Value;
  return true;
}

bool checkerTest::handleGet(CheckerContext &C, const CallEvent &Call) const {
  ProgramStateRef State = C.getState();
  State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), g_value);
  C.addTransition(State);
  return true;
}

bool checkerTest::evalCall(const CallEvent &Call, CheckerContext &C) const {
    const FnHandler *Handler = Functions.lookup(Call);
    if (Handler) {
        return (this->**Handler)(C, Call);
    }
    return false;
}

void ento::registercheckerTest(CheckerManager &mgr) {
  mgr.registerChecker<checkerTest>();
}

bool ento::shouldRegistercheckerTest(const CheckerManager &mgr) {
  if (mgr.getLangOpts().CPlusPlus)
    return true;
  return false;
}

测试以下代码时触发断言崩溃:

void set(const int& value);
const int& get();

int main()
{
    set(0);
    int res = get();
    return res;
}

崩溃信息:

Assertion `!isa<NonLoc>(location) && "location cannot be a NonLoc."' failed.

问题原因

  1. 返回类型不匹配:get()的返回类型是const int&,静态分析器期望该函数的返回值是内存位置(Loc类型的SVal),但原代码中保存的g_value是从参数加载的数值(NonLoc类型)。当后续代码尝试对get()的返回值执行加载操作时,分析器发现传入的是NonLoc而非Loc,触发断言。
  2. 全局变量存储状态错误:使用全局变量g_value保存状态违反了静态分析的路径敏感性,不同分析路径的状态会互相覆盖,导致分析结果错误。正确的做法是将状态存储在ProgramState中。

修正方案

核心思路

  • 模拟set和get背后的内部变量:创建一个符号化的内存位置代表这个内部变量
  • 在handleSet中,将传入参数的值存储到这个内部变量的位置,并将该位置保存到ProgramState
  • 在handleGet中,返回这个内部变量的位置,作为函数的返回值

修正后的完整代码

#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"

using namespace clang;
using namespace ento;

namespace {

class checkerTest : public Checker<eval::Call> {
  bool handleSet(CheckerContext &C, const CallEvent &Call) const;
  bool handleGet(CheckerContext &C, const CallEvent &Call) const;

public:
  bool evalCall(const CallEvent &Call, CheckerContext &C) const;

  using FnHandler = bool (checkerTest::*)(CheckerContext &, const CallEvent &Call) const;
  CallDescriptionMap<FnHandler> Functions = {
      {{{"set"}, 1}, &checkerTest::handleSet},
      {{{"get"}, 0}, &checkerTest::handleGet},
  };
};

// 注册用于存储内部变量位置的ProgramState trait
REGISTER_TRAIT_WITH_PROGRAMSTATE(InternalVarLoc, Loc)

} // namespace

bool checkerTest::handleSet(CheckerContext &C, const CallEvent &Call) const {
  ProgramStateRef State = C.getState();
  
  // 获取传入参数的位置和值
  SVal ArgLoc = Call.getArgSVal(0).castAs<Loc>();
  QualType ArgTy = Call.getArgExpr(0)->getType().getNonReferenceType();
  SVal ArgValue = State->getSVal(ArgLoc, ArgTy);

  // 创建一个符号化的位置代表内部变量
  const LocationContext *LCtx = C.getLocationContext();
  QualType IntTy = C.getASTContext().IntTy;
  SymbolRef InternalVarSym = C.getSymbolManager().getFreshSymbol(IntTy, LCtx, Call.getOriginExpr());
  Loc InternalVarLoc = C.getSValBuilder().makeSymbolVal(InternalVarSym, IntTy).castAs<Loc>();

  // 将参数的值存储到内部变量的位置
  State = State->setSVal(InternalVarLoc, ArgValue);
  // 将内部变量的位置保存到ProgramState
  State = State->set<InternalVarLoc>(InternalVarLoc);

  C.addTransition(State);
  return true;
}

bool checkerTest::handleGet(CheckerContext &C, const CallEvent &Call) const {
  ProgramStateRef State = C.getState();
  
  // 从ProgramState中取出内部变量的位置
  Loc InternalVarLoc = State->get<InternalVarLoc>();
  if (!InternalVarLoc.isValid()) {
    // 如果还没调用过set,这里可以根据需求处理,比如返回未知值
    return false;
  }

  // 将内部变量的位置绑定为get()函数的返回值(因为返回的是引用)
  State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), InternalVarLoc);
  C.addTransition(State);
  return true;
}

bool checkerTest::evalCall(const CallEvent &Call, CheckerContext &C) const {
  const FnHandler *Handler = Functions.lookup(Call);
  if (Handler) {
    return (this->**Handler)(C, Call);
  }
  return false;
}

void ento::registercheckerTest(CheckerManager &mgr) {
  mgr.registerChecker<checkerTest>();
}

bool ento::shouldRegistercheckerTest(const CheckerManager &mgr) {
  return mgr.getLangOpts().CPlusPlus;
}

关键修改点说明

  1. REGISTER_TRAIT_WITH_PROGRAMSTATE:自定义ProgramState的扩展字段,用于存储内部变量的位置,替代原有的全局变量,保证路径敏感的状态管理。
  2. handleSet逻辑:创建新的符号化位置代表内部变量,将传入参数的值写入该位置,并将位置存入ProgramState。
  3. handleGet逻辑:从ProgramState取出内部变量的位置,将其绑定为get()函数的返回值(符合引用类型的要求,返回内存位置而非值)。

内容的提问来源于stack exchange,提问作者nevilad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 20:45:53