PVS-સ્ટુડિયો વિશ્લેષકનો ઉપયોગ કરીને LLVM 8 માં ભૂલો શોધવી

PVS-સ્ટુડિયો વિશ્લેષકનો ઉપયોગ કરીને LLVM 8 માં ભૂલો શોધવી
અમારા પીવીએસ-સ્ટુડિયો વિશ્લેષકનો ઉપયોગ કરીને એલએલવીએમ પ્રોજેક્ટની છેલ્લી કોડ તપાસને બે વર્ષથી વધુ સમય વીતી ગયો છે. ચાલો ખાતરી કરીએ કે PVS-સ્ટુડિયો વિશ્લેષક હજુ પણ ભૂલો અને સંભવિત નબળાઈઓને ઓળખવા માટેનું અગ્રણી સાધન છે. આ કરવા માટે, અમે LLVM 8.0.0 રિલીઝમાં નવી ભૂલો તપાસીશું અને શોધીશું.

લેખ લખવાનો છે

સાચું કહું તો, હું આ લેખ લખવા માંગતો ન હતો. એવા પ્રોજેક્ટ વિશે લખવું રસપ્રદ નથી કે જે અમે પહેલાથી જ ઘણી વખત તપાસ્યું છે (1, 2, 3). કંઈક નવું લખવું વધુ સારું છે, પરંતુ મારી પાસે કોઈ વિકલ્પ નથી.

દર વખતે LLVM નું નવું વર્ઝન રીલીઝ કે અપડેટ થાય છે રણકાર સ્ટેટિક વિશ્લેષક, અમને અમારા મેઇલમાં નીચેના પ્રકારના પ્રશ્નો પ્રાપ્ત થાય છે:

જુઓ, ક્લેંગ સ્ટેટિક એનાલાઈઝરનું નવું વર્ઝન નવી ભૂલો શોધવાનું શીખી ગયું છે! મને લાગે છે કે PVS-Studio નો ઉપયોગ કરવાની સુસંગતતા ઘટી રહી છે. ક્લેંગ પહેલા કરતાં વધુ ભૂલો શોધે છે અને PVS-સ્ટુડિયોની ક્ષમતાઓ સાથે મેળવે છે. તમે આ વિશે શું વિચારો છો?

આ માટે હું હંમેશા કંઈક જવાબ આપવા માંગુ છું:

અમે પણ નિષ્ક્રિય નથી બેસતા! અમે પીવીએસ-સ્ટુડિયો વિશ્લેષકની ક્ષમતાઓમાં નોંધપાત્ર સુધારો કર્યો છે. તેથી ચિંતા કરશો નહીં, અમે પહેલાની જેમ નેતૃત્વ કરવાનું ચાલુ રાખીએ છીએ.

કમનસીબે, આ એક ખરાબ જવાબ છે. તેમાં કોઈ પુરાવા નથી. અને તેથી જ હવે હું આ લેખ લખી રહ્યો છું. તેથી, LLVM પ્રોજેક્ટની ફરી એકવાર ચકાસણી કરવામાં આવી છે અને તેમાં વિવિધ પ્રકારની ભૂલો જોવા મળી છે. હું હવે તે દર્શાવીશ જે મને રસપ્રદ લાગતું હતું. રણકાર સ્થિર વિશ્લેષક આ ભૂલો શોધી શકતું નથી (અથવા તેની સહાયથી આમ કરવું અત્યંત અસુવિધાજનક છે). પરંતુ અમે કરી શકીએ છીએ. તદુપરાંત, મેં એક સાંજે આ બધી ભૂલો શોધી અને લખી.

પરંતુ લેખ લખવામાં ઘણા અઠવાડિયા લાગ્યા. આ બધું લખાણમાં મૂકવા માટે હું મારી જાતને લાવી શક્યો નથી :).

માર્ગ દ્વારા, જો તમને PVS-સ્ટુડિયો વિશ્લેષકમાં ભૂલો અને સંભવિત નબળાઈઓને ઓળખવા માટે કઈ તકનીકોનો ઉપયોગ કરવામાં આવે છે તેમાં રસ હોય, તો હું આની સાથે પરિચિત થવાનું સૂચન કરું છું. નૉૅધ.

નવા અને જૂના ડાયગ્નોસ્ટિક્સ

પહેલેથી જ નોંધ્યું છે તેમ, લગભગ બે વર્ષ પહેલાં એલએલવીએમ પ્રોજેક્ટની ફરી એકવાર તપાસ કરવામાં આવી હતી, અને જે ભૂલો મળી હતી તેને સુધારવામાં આવી હતી. હવે આ લેખ ભૂલોની નવી બેચ રજૂ કરશે. શા માટે નવી ભૂલો મળી? આના માટે 3 કારણો છે:

  1. LLVM પ્રોજેક્ટ વિકસિત થઈ રહ્યો છે, જૂનો કોડ બદલી રહ્યો છે અને નવો કોડ ઉમેરી રહ્યો છે. સ્વાભાવિક રીતે, સંશોધિત અને લેખિત કોડમાં નવી ભૂલો છે. આ સ્પષ્ટપણે દર્શાવે છે કે સ્થિર વિશ્લેષણનો ઉપયોગ નિયમિતપણે થવો જોઈએ, અને પ્રસંગોપાત નહીં. અમારા લેખો પીવીએસ-સ્ટુડિયો વિશ્લેષકની ક્ષમતાઓ સારી રીતે દર્શાવે છે, પરંતુ આને કોડ ગુણવત્તા સુધારવા અને ભૂલો સુધારવાની કિંમત ઘટાડવા સાથે કોઈ લેવાદેવા નથી. નિયમિતપણે સ્ટેટિક કોડ વિશ્લેષકનો ઉપયોગ કરો!
  2. અમે હાલના ડાયગ્નોસ્ટિક્સને અંતિમ સ્વરૂપ આપી રહ્યા છીએ અને તેમાં સુધારો કરી રહ્યા છીએ. તેથી, વિશ્લેષક ભૂલોને ઓળખી શકે છે જે તેણે અગાઉના સ્કેન દરમિયાન નોંધ્યું ન હતું.
  3. PVS-Studio માં નવા ડાયગ્નોસ્ટિક્સ દેખાયા છે જે 2 વર્ષ પહેલા અસ્તિત્વમાં ન હતા. PVS-સ્ટુડિયોના વિકાસને સ્પષ્ટ રીતે બતાવવા માટે મેં તેમને અલગ વિભાગમાં પ્રકાશિત કરવાનું નક્કી કર્યું.

2 વર્ષ પહેલાં અસ્તિત્વમાં રહેલા ડાયગ્નોસ્ટિક્સ દ્વારા ઓળખવામાં આવેલી ખામી

ફ્રેગમેન્ટ N1: કોપી-પેસ્ટ કરો

static bool ShouldUpgradeX86Intrinsic(Function *F, StringRef Name) {
  if (Name == "addcarryx.u32" || // Added in 8.0
    ....
    Name == "avx512.mask.cvtps2pd.128" || // Added in 7.0
    Name == "avx512.mask.cvtps2pd.256" || // Added in 7.0
    Name == "avx512.cvtusi2sd" || // Added in 7.0
    Name.startswith("avx512.mask.permvar.") || // Added in 7.0     // <=
    Name.startswith("avx512.mask.permvar.") || // Added in 7.0     // <=
    Name == "sse2.pmulu.dq" || // Added in 7.0
    Name == "sse41.pmuldq" || // Added in 7.0
    Name == "avx2.pmulu.dq" || // Added in 7.0
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V501 [CWE-570] '||' ની ડાબી અને જમણી બાજુએ 'Name.startswith("avx512.mask.permvar.")' સમાન પેટા-અભિવ્યક્તિઓ છે. ઓપરેટર AutoUpgrade.cpp 73

તે બે વાર તપાસવામાં આવે છે કે નામ સબસ્ટ્રિંગ "avx512.mask.permvar" થી શરૂ થાય છે. બીજા ચેકમાં, તેઓ દેખીતી રીતે કંઈક બીજું લખવા માંગતા હતા, પરંતુ કૉપિ કરેલા ટેક્સ્ટને સુધારવાનું ભૂલી ગયા હતા.

ફ્રેગમેન્ટ N2: ટાઈપો

enum CXNameRefFlags {
  CXNameRange_WantQualifier = 0x1,
  CXNameRange_WantTemplateArgs = 0x2,
  CXNameRange_WantSinglePiece = 0x4
};

void AnnotateTokensWorker::HandlePostPonedChildCursor(
    CXCursor Cursor, unsigned StartTokenIndex) {
  const auto flags = CXNameRange_WantQualifier | CXNameRange_WantQualifier;
  ....
}

ચેતવણી PVS-Studio: V501 '|' ની ડાબી અને જમણી બાજુએ 'CXNameRange_WantQualifier' સમાન પેટા-અભિવ્યક્તિઓ છે. ઓપરેટર CIndex.cpp 7245

ટાઈપોને લીધે, સમાન નામના સ્થિરાંકનો બે વાર ઉપયોગ થાય છે CXNameRange_WantQualifier.

ફ્રેગમેન્ટ N3: ઓપરેટરની અગ્રતા સાથે મૂંઝવણ

int PPCTTIImpl::getVectorInstrCost(unsigned Opcode, Type *Val, unsigned Index) {
  ....
  if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian() ? 1 : 0)
    return 0;
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V502 [CWE-783] કદાચ '?:' ઓપરેટર અપેક્ષા કરતા અલગ રીતે કામ કરે છે. '?:' ઓપરેટર '==' ઓપરેટર કરતાં ઓછી પ્રાથમિકતા ધરાવે છે. PPCTargetTransformInfo.cpp 404

મારા મતે, આ એક ખૂબ જ સુંદર ભૂલ છે. હા, હું જાણું છું કે મારી પાસે સૌંદર્ય વિશે વિચિત્ર વિચારો છે :).

હવે, અનુસાર ઓપરેટરની પ્રાથમિકતાઓ, અભિવ્યક્તિનું મૂલ્યાંકન નીચે પ્રમાણે કરવામાં આવે છે:

(ISD == ISD::EXTRACT_VECTOR_ELT && (Index == ST->isLittleEndian())) ? 1 : 0

વ્યવહારિક દૃષ્ટિકોણથી, આવી સ્થિતિનો કોઈ અર્થ નથી, કારણ કે તેને ઘટાડી શકાય છે:

(ISD == ISD::EXTRACT_VECTOR_ELT && Index == ST->isLittleEndian())

આ એક સ્પષ્ટ ભૂલ છે. મોટે ભાગે, તેઓ ચલ સાથે 0/1 ની સરખામણી કરવા માંગતા હતા ઇન્ડેક્સ. કોડને ઠીક કરવા માટે તમારે ટર્નરી ઓપરેટરની આસપાસ કૌંસ ઉમેરવાની જરૂર છે:

if (ISD == ISD::EXTRACT_VECTOR_ELT && Index == (ST->isLittleEndian() ? 1 : 0))

માર્ગ દ્વારા, ટર્નરી ઓપરેટર ખૂબ જ ખતરનાક છે અને તાર્કિક ભૂલોને ઉશ્કેરે છે. તેની સાથે ખૂબ કાળજી રાખો અને કૌંસ સાથે લોભી ન બનો. મેં આ વિષયને વધુ વિગતવાર જોયો અહીં, પ્રકરણમાં “?: ઓપરેટરથી સાવધ રહો અને તેને કૌંસમાં બંધ કરો.”

ફ્રેગમેન્ટ N4, N5: નલ પોઇન્ટર

Init *TGParser::ParseValue(Record *CurRec, RecTy *ItemType, IDParseMode Mode) {
  ....
  TypedInit *LHS = dyn_cast<TypedInit>(Result);
  ....
  LHS = dyn_cast<TypedInit>(
    UnOpInit::get(UnOpInit::CAST, LHS, StringRecTy::get())
      ->Fold(CurRec));
  if (!LHS) {
    Error(PasteLoc, Twine("can't cast '") + LHS->getAsString() +
                    "' to string");
    return nullptr;
  }
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V522 [CWE-476] નલ પોઈન્ટર 'LHS' નું ડિરેફરન્સિંગ થઈ શકે છે. TGParser.cpp 2152

જો નિર્દેશક એલએચએસ શૂન્ય છે, ચેતવણી જારી કરવી જોઈએ. જો કે, તેના બદલે, આ જ નલ પોઇંટરને સંદર્ભિત કરવામાં આવશે: LHS->getAsString().

આ એક ખૂબ જ લાક્ષણિક પરિસ્થિતિ છે જ્યારે કોઈ ભૂલ હેન્ડલરમાં છુપાયેલી હોય છે, કારણ કે કોઈ તેનું પરીક્ષણ કરતું નથી. સ્ટેટિક વિશ્લેષકો તમામ પહોંચી શકાય તેવા કોડને તપાસે છે, પછી ભલે તે કેટલી વાર ઉપયોગમાં લેવાય. સ્થિર વિશ્લેષણ અન્ય પરીક્ષણ અને ભૂલ સંરક્ષણ તકનીકોને કેવી રીતે પૂરક બનાવે છે તેનું આ એક ખૂબ જ સારું ઉદાહરણ છે.

સમાન પોઇન્ટર હેન્ડલિંગ ભૂલ RHS નીચે આપેલા કોડમાં મંજૂર: V522 [CWE-476] નલ પોઈન્ટર 'RHS' નું ડિરેફરન્સિંગ થઈ શકે છે. TGParser.cpp 2186

ફ્રેગમેન્ટ N6: ખસેડ્યા પછી પોઇન્ટરનો ઉપયોગ કરવો

static Expected<bool>
ExtractBlocks(....)
{
  ....
  std::unique_ptr<Module> ProgClone = CloneModule(BD.getProgram(), VMap);
  ....
  BD.setNewProgram(std::move(ProgClone));                                // <=
  MiscompiledFunctions.clear();

  for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {
    Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);  // <=
    assert(NewF && "Function not found??");
    MiscompiledFunctions.push_back(NewF);
  }
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V522 [CWE-476] નલ પોઈન્ટર 'પ્રોગક્લોન' નું ડિરેફરન્સિંગ થઈ શકે છે. Miscompilation.cpp 601

શરૂઆતમાં એક સ્માર્ટ પોઇન્ટર પ્રોગક્લોન ઑબ્જેક્ટની માલિકી બંધ કરે છે:

BD.setNewProgram(std::move(ProgClone));

હકીકતમાં, હવે પ્રોગક્લોન એક નલ પોઇન્ટર છે. તેથી, એક નલ પોઈન્ટર ડિરેફરન્સ ફક્ત નીચે આવવું જોઈએ:

Function *NewF = ProgClone->getFunction(MisCompFunctions[i].first);

પરંતુ, વાસ્તવમાં, આવું થશે નહીં! નોંધ કરો કે લૂપ વાસ્તવમાં એક્ઝિક્યુટ થયેલ નથી.

કન્ટેનરની શરૂઆતમાં ખોટી રીતે સંકલિત કાર્યો સાફ કર્યું:

MiscompiledFunctions.clear();

આગળ, આ કન્ટેનરનું કદ લૂપ સ્થિતિમાં વપરાય છે:

for (unsigned i = 0, e = MisCompFunctions.size(); i != e; ++i) {

તે જોવાનું સરળ છે કે લૂપ શરૂ થતું નથી. મને લાગે છે કે આ પણ એક બગ છે અને કોડ અલગ રીતે લખવો જોઈએ.

એવું લાગે છે કે અમે ભૂલોની તે પ્રખ્યાત સમાનતાનો સામનો કર્યો છે! એક ભૂલ બીજાને ઢાંકી દે છે :).

ફ્રેગમેન્ટ N7: ખસેડ્યા પછી પોઇન્ટરનો ઉપયોગ કરવો

static Expected<bool> TestOptimizer(BugDriver &BD, std::unique_ptr<Module> Test,
                                    std::unique_ptr<Module> Safe) {
  outs() << "  Optimizing functions being tested: ";
  std::unique_ptr<Module> Optimized =
      BD.runPassesOn(Test.get(), BD.getPassesToRun());
  if (!Optimized) {
    errs() << " Error running this sequence of passes"
           << " on the input program!n";
    BD.setNewProgram(std::move(Test));                       // <=
    BD.EmitProgressBitcode(*Test, "pass-error", false);      // <=
    if (Error E = BD.debugOptimizerCrash())
      return std::move(E);
    return false;
  }
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V522 [CWE-476] નલ પોઈન્ટર 'ટેસ્ટ' નું ડિરેફરન્સિંગ થઈ શકે છે. Miscompilation.cpp 709

ફરી એ જ સ્થિતિ. શરૂઆતમાં, ઑબ્જેક્ટના સમાવિષ્ટોને ખસેડવામાં આવે છે, અને પછી તેનો ઉપયોગ થાય છે જાણે કંઈ બન્યું જ ન હોય. C++ માં મૂવમેન્ટ સિમેન્ટિક્સ દેખાયા પછી હું પ્રોગ્રામ કોડમાં આ પરિસ્થિતિ વધુ અને વધુ વખત જોઉં છું. તેથી જ મને C++ ભાષા ગમે છે! તમારા પોતાના પગને શૂટ કરવાની વધુ અને વધુ નવી રીતો છે. પીવીએસ-સ્ટુડિયો વિશ્લેષક પાસે હંમેશા કામ હશે :).

ફ્રેગમેન્ટ N8: નલ પોઇન્ટર

void FunctionDumper::dump(const PDBSymbolTypeFunctionArg &Symbol) {
  uint32_t TypeId = Symbol.getTypeId();
  auto Type = Symbol.getSession().getSymbolById(TypeId);
  if (Type)
    Printer << "<unknown-type>";
  else
    Type->dump(*this);
}

PVS-સ્ટુડિયો ચેતવણી: V522 [CWE-476] નલ પોઈન્ટર 'ટાઈપ' નું ડિરેફરન્સિંગ થઈ શકે છે. PrettyFunctionDumper.cpp 233

એરર હેન્ડલર્સ ઉપરાંત, ડિબગીંગ પ્રિન્ટઆઉટ ફંક્શન સામાન્ય રીતે ચકાસવામાં આવતા નથી. અમારી સામે આવો જ એક કિસ્સો છે. ફંક્શન વપરાશકર્તાની રાહ જોઈ રહ્યું છે, જે તેની સમસ્યાઓ હલ કરવાને બદલે તેને ઠીક કરવાની ફરજ પાડશે.

સાચું

if (Type)
  Type->dump(*this);
else
  Printer << "<unknown-type>";

ફ્રેગમેન્ટ N9: નલ પોઇન્ટર

void SearchableTableEmitter::collectTableEntries(
    GenericTable &Table, const std::vector<Record *> &Items) {
  ....
  RecTy *Ty = resolveTypes(Field.RecType, TI->getType());
  if (!Ty)                                                              // <=
    PrintFatalError(Twine("Field '") + Field.Name + "' of table '" +
                    Table.Name + "' has incompatible type: " +
                    Ty->getAsString() + " vs. " +                       // <=
                    TI->getType()->getAsString());
   ....
}

PVS-સ્ટુડિયો ચેતવણી: V522 [CWE-476] નલ પોઈન્ટર 'Ty' નું ડિરેફરન્સિંગ થઈ શકે છે. SearchableTableEmitter.cpp 614

મને લાગે છે કે બધું સ્પષ્ટ છે અને સમજૂતીની જરૂર નથી.

ફ્રેગમેન્ટ N10: ટાઈપો

bool FormatTokenLexer::tryMergeCSharpNullConditionals() {
  ....
  auto &Identifier = *(Tokens.end() - 2);
  auto &Question = *(Tokens.end() - 1);
  ....
  Identifier->ColumnWidth += Question->ColumnWidth;
  Identifier->Type = Identifier->Type;                    // <=
  Tokens.erase(Tokens.end() - 1);
  return true;
}

PVS-સ્ટુડિયો ચેતવણી: V570 'આઇડેન્ટિફાયર->ટાઇપ' વેરીએબલ પોતાને સોંપેલ છે. FormatTokenLexer.cpp 249

પોતાને ચલ સોંપવાનો કોઈ અર્થ નથી. મોટે ભાગે તેઓ લખવા માંગતા હતા:

Identifier->Type = Question->Type;

ફ્રેગમેન્ટ N11: શંકાસ્પદ વિરામ

void SystemZOperand::print(raw_ostream &OS) const {
  switch (Kind) {
    break;
  case KindToken:
    OS << "Token:" << getToken();
    break;
  case KindReg:
    OS << "Reg:" << SystemZInstPrinter::getRegisterName(getReg());
    break;
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V622 [CWE-478] 'સ્વીચ' સ્ટેટમેન્ટનું નિરીક્ષણ કરવાનું વિચારો. શક્ય છે કે પ્રથમ 'કેસ' ઓપરેટર ખૂટે છે. SystemZAsmParser.cpp 652

શરૂઆતમાં ખૂબ જ શંકાસ્પદ ઓપરેટર છે વિરામ. શું તમે અહીં બીજું કંઈક લખવાનું ભૂલી ગયા છો?

ફ્રેગમેન્ટ N12: ડિરેફરન્સિંગ પછી પોઇન્ટર તપાસી રહ્યું છે

InlineCost AMDGPUInliner::getInlineCost(CallSite CS) {
  Function *Callee = CS.getCalledFunction();
  Function *Caller = CS.getCaller();
  TargetTransformInfo &TTI = TTIWP->getTTI(*Callee);

  if (!Callee || Callee->isDeclaration())
    return llvm::InlineCost::getNever("undefined callee");
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V595 [CWE-476] 'કૅલી' પોઇન્ટરનો ઉપયોગ nullptr સામે ચકાસવામાં આવે તે પહેલાં કરવામાં આવ્યો હતો. રેખાઓ તપાસો: 172, 174. AMDGPUInline.cpp 172

નિર્દેશક કેલી જ્યારે ફંક્શનને બોલાવવામાં આવે છે ત્યારે શરૂઆતમાં એ ડિરેફરન્સ્ડ છે મેળવો.

અને પછી તે તારણ આપે છે કે આ નિર્દેશકને સમાનતા માટે તપાસવું જોઈએ nullptr:

if (!Callee || Callee->isDeclaration())

પણ બહુ મોડું થઈ ગયું...

ફ્રેગમેન્ટ N13 - N...: ડિરેફરન્સિંગ પછી પોઇન્ટર તપાસી રહ્યું છે

અગાઉના કોડ ફ્રેગમેન્ટમાં ચર્ચા કરેલી પરિસ્થિતિ અનન્ય નથી. તે અહીં દેખાય છે:

static Value *optimizeDoubleFP(CallInst *CI, IRBuilder<> &B,
                               bool isBinary, bool isPrecise = false) {
  ....
  Function *CalleeFn = CI->getCalledFunction();
  StringRef CalleeNm = CalleeFn->getName();                 // <=
  AttributeList CalleeAt = CalleeFn->getAttributes();
  if (CalleeFn && !CalleeFn->isIntrinsic()) {               // <=
  ....
}

PVS-Studio ચેતવણી: V595 [CWE-476] 'CalleeFn' પોઇન્ટરનો ઉપયોગ nullptr સામે ચકાસવામાં આવે તે પહેલાં કરવામાં આવ્યો હતો. રેખાઓ તપાસો: 1079, 1081. SimplifyLibCalls.cpp 1079

અને અહીં:

void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
                            const Decl *Tmpl, Decl *New,
                            LateInstantiatedAttrVec *LateAttrs,
                            LocalInstantiationScope *OuterMostScope) {
  ....
  NamedDecl *ND = dyn_cast<NamedDecl>(New);
  CXXRecordDecl *ThisContext =
    dyn_cast_or_null<CXXRecordDecl>(ND->getDeclContext());         // <=
  CXXThisScopeRAII ThisScope(*this, ThisContext, Qualifiers(),
                             ND && ND->isCXXInstanceMember());     // <=
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V595 [CWE-476] 'ND' પોઇન્ટર nullptr સામે ચકાસવામાં આવે તે પહેલાં તેનો ઉપયોગ કરવામાં આવ્યો હતો. રેખાઓ તપાસો: 532, 534. SemaTemplateInstantiateDecl.cpp 532

અને અહીં:

  • V595 [CWE-476] nullptr સામે ચકાસવામાં આવે તે પહેલાં 'U' પોઇન્ટરનો ઉપયોગ કરવામાં આવ્યો હતો. રેખાઓ તપાસો: 404, 407. DWARFormValue.cpp 404
  • V595 [CWE-476] 'ND' પોઇન્ટર nullptr સામે ચકાસવામાં આવે તે પહેલાં તેનો ઉપયોગ કરવામાં આવ્યો હતો. રેખાઓ તપાસો: 2149, 2151. SemaTemplateInstantiate.cpp 2149

અને પછી મને V595 નંબર સાથેની ચેતવણીઓનો અભ્યાસ કરવામાં રસ ન રહ્યો. તેથી મને ખબર નથી કે અહીં સૂચિબદ્ધ ભૂલો સિવાય અન્ય સમાન ભૂલો છે કે નહીં. મોટે ભાગે ત્યાં છે.

ફ્રેગમેન્ટ N17, N18: શંકાસ્પદ પાળી

static inline bool processLogicalImmediate(uint64_t Imm, unsigned RegSize,
                                           uint64_t &Encoding) {
  ....
  unsigned Size = RegSize;
  ....
  uint64_t NImms = ~(Size-1) << 1;
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V629 [CWE-190] '~(કદ - 1) << 1' અભિવ્યક્તિનું નિરીક્ષણ કરવાનું વિચારો. 32-બીટ પ્રકારમાં અનુગામી વિસ્તરણ સાથે 64-બીટ મૂલ્યનું બિટ શિફ્ટિંગ. AArch64AddressingModes.h 260

તે બગ ન પણ હોઈ શકે અને કોડ બરાબર હેતુ મુજબ કામ કરે છે. પરંતુ આ સ્પષ્ટપણે ખૂબ જ શંકાસ્પદ સ્થળ છે અને તેની તપાસ કરવાની જરૂર છે.

ચાલો ચલ કહીએ માપ 16 ની બરાબર છે, અને પછી કોડના લેખકે તેને ચલમાં મેળવવાની યોજના બનાવી છે NImms અર્થ:

1111111111111111111111111111111111111111111111111111111111100000

જો કે, વાસ્તવમાં પરિણામ આ હશે:

0000000000000000000000000000000011111111111111111111111111100000

હકીકત એ છે કે તમામ ગણતરીઓ 32-બીટ સહી વગરના પ્રકારનો ઉપયોગ કરીને થાય છે. અને માત્ર ત્યારે જ, આ 32-બીટ સહી વિનાનો પ્રકાર ગર્ભિત રીતે વિસ્તૃત કરવામાં આવશે uint64_t. આ કિસ્સામાં, સૌથી નોંધપાત્ર બિટ્સ શૂન્ય હશે.

તમે આની જેમ પરિસ્થિતિને ઠીક કરી શકો છો:

uint64_t NImms = ~static_cast<uint64_t>(Size-1) << 1;

સમાન પરિસ્થિતિ: V629 [CWE-190] 'Immr << 6' અભિવ્યક્તિનું નિરીક્ષણ કરવાનું વિચારો. 32-બીટ પ્રકારમાં અનુગામી વિસ્તરણ સાથે 64-બીટ મૂલ્યનું બિટ શિફ્ટિંગ. AArch64AddressingModes.h 269

ફ્રેગમેન્ટ N19: કીવર્ડ ખૂટે છે બીજું?

void AMDGPUAsmParser::cvtDPP(MCInst &Inst, const OperandVector &Operands) {
  ....
  if (Op.isReg() && Op.Reg.RegNo == AMDGPU::VCC) {
    // VOP2b (v_add_u32, v_sub_u32 ...) dpp use "vcc" token.
    // Skip it.
    continue;
  } if (isRegOrImmWithInputMods(Desc, Inst.getNumOperands())) {    // <=
    Op.addRegWithFPInputModsOperands(Inst, 2);
  } else if (Op.isDPPCtrl()) {
    Op.addImmOperands(Inst, 1);
  } else if (Op.isImm()) {
    // Handle optional arguments
    OptionalIdx[Op.getImmTy()] = I;
  } else {
    llvm_unreachable("Invalid operand type");
  }
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V646 [CWE-670] એપ્લિકેશનના તર્કનું નિરીક્ષણ કરવાનું વિચારો. શક્ય છે કે 'બીજો' કીવર્ડ ખૂટે છે. AMDGPUAsmParser.cpp 5655

અહીં કોઈ ભૂલ નથી. ત્યારથી પ્રથમ ના બ્લોક if સાથે સમાપ્ત થાય છે ચાલુ, પછી તે કોઈ વાંધો નથી, ત્યાં એક કીવર્ડ છે બીજું અથવા નહીં. કોઈપણ રીતે કોડ સમાન કાર્ય કરશે. હજી ચૂકી ગયો બીજું કોડને વધુ અસ્પષ્ટ અને જોખમી બનાવે છે. જો ભવિષ્યમાં ચાલુ અદૃશ્ય થઈ જાય છે, કોડ સંપૂર્ણપણે અલગ રીતે કામ કરવાનું શરૂ કરશે. મારા મતે ઉમેરવું વધુ સારું છે બીજું.

Fragment N20: સમાન પ્રકારના ચાર ટાઈપો

LLVM_DUMP_METHOD void Symbol::dump(raw_ostream &OS) const {
  std::string Result;
  if (isUndefined())
    Result += "(undef) ";
  if (isWeakDefined())
    Result += "(weak-def) ";
  if (isWeakReferenced())
    Result += "(weak-ref) ";
  if (isThreadLocalValue())
    Result += "(tlv) ";
  switch (Kind) {
  case SymbolKind::GlobalSymbol:
    Result + Name.str();                        // <=
    break;
  case SymbolKind::ObjectiveCClass:
    Result + "(ObjC Class) " + Name.str();      // <=
    break;
  case SymbolKind::ObjectiveCClassEHType:
    Result + "(ObjC Class EH) " + Name.str();   // <=
    break;
  case SymbolKind::ObjectiveCInstanceVariable:
    Result + "(ObjC IVar) " + Name.str();       // <=
    break;
  }
  OS << Result;
}

PVS-સ્ટુડિયો ચેતવણીઓ:

  • V655 [CWE-480] શબ્દમાળાઓ સંકલિત હતી પરંતુ તેનો ઉપયોગ થતો નથી. 'Result + Name.str()' અભિવ્યક્તિનું નિરીક્ષણ કરવાનું વિચારો. Symbol.cpp 32
  • V655 [CWE-480] શબ્દમાળાઓ સંકલિત હતી પરંતુ તેનો ઉપયોગ થતો નથી. 'પરિણામ + "(ObjC વર્ગ)" + Name.str()' અભિવ્યક્તિનું નિરીક્ષણ કરવાનું વિચારો. Symbol.cpp 35
  • V655 [CWE-480] શબ્દમાળાઓ સંકલિત હતી પરંતુ તેનો ઉપયોગ થતો નથી. 'પરિણામ + "(ObjC વર્ગ EH) " + Name.str()' અભિવ્યક્તિનું નિરીક્ષણ કરવાનું વિચારો. Symbol.cpp 38
  • V655 [CWE-480] શબ્દમાળાઓ સંકલિત હતી પરંતુ તેનો ઉપયોગ થતો નથી. 'પરિણામ + "(ObjC IVar)" + Name.str()' અભિવ્યક્તિનું નિરીક્ષણ કરવાનું વિચારો. Symbol.cpp 41

અકસ્માતે, += ઓપરેટરને બદલે + ઓપરેટરનો ઉપયોગ થાય છે. પરિણામ એ ડિઝાઇન્સ છે જે અર્થહીન છે.

ફ્રેગમેન્ટ N21: અવ્યાખ્યાયિત વર્તન

static void getReqFeatures(std::map<StringRef, int> &FeaturesMap,
                           const std::vector<Record *> &ReqFeatures) {
  for (auto &R : ReqFeatures) {
    StringRef AsmCondString = R->getValueAsString("AssemblerCondString");

    SmallVector<StringRef, 4> Ops;
    SplitString(AsmCondString, Ops, ",");
    assert(!Ops.empty() && "AssemblerCondString cannot be empty");

    for (auto &Op : Ops) {
      assert(!Op.empty() && "Empty operator");
      if (FeaturesMap.find(Op) == FeaturesMap.end())
        FeaturesMap[Op] = FeaturesMap.size();
    }
  }
}

ખતરનાક કોડ જાતે શોધવાનો પ્રયાસ કરો. અને ધ્યાન વિચલિત કરવા માટે આ એક ચિત્ર છે જેથી તરત જ જવાબ જોવા ન મળે:

PVS-સ્ટુડિયો વિશ્લેષકનો ઉપયોગ કરીને LLVM 8 માં ભૂલો શોધવી

PVS-સ્ટુડિયો ચેતવણી: V708 [CWE-758] ખતરનાક બાંધકામનો ઉપયોગ થાય છે: 'FeaturesMap[Op] = FeaturesMap.size()', જ્યાં 'FeaturesMap' 'નકશો' વર્ગનો છે. આ અવ્યાખ્યાયિત વર્તન તરફ દોરી શકે છે. RISCVCompressInstEmitter.cpp 490

સમસ્યા રેખા:

FeaturesMap[Op] = FeaturesMap.size();

જો તત્વ Op મળ્યું નથી, પછી નકશામાં એક નવું તત્વ બનાવવામાં આવે છે અને આ નકશામાં તત્વોની સંખ્યા ત્યાં લખવામાં આવે છે. ફંક્શનને બોલાવવામાં આવશે કે કેમ તે ફક્ત અજ્ઞાત છે કદ નવું તત્વ ઉમેરતા પહેલા અથવા પછી.

ફ્રેગમેન્ટ N22-N24: પુનરાવર્તિત સોંપણીઓ

Error MachOObjectFile::checkSymbolTable() const {
  ....
  } else {
    MachO::nlist STE = getSymbolTableEntry(SymDRI);
    NType = STE.n_type;                              // <=
    NType = STE.n_type;                              // <=
    NSect = STE.n_sect;
    NDesc = STE.n_desc;
    NStrx = STE.n_strx;
    NValue = STE.n_value;
  }
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V519 [CWE-563] 'NType' ચલને અનુક્રમે બે વાર મૂલ્યો અસાઇન કરવામાં આવે છે. કદાચ આ એક ભૂલ છે. રેખાઓ તપાસો: 1663, 1664. MachOObjectFile.cpp 1664

મને નથી લાગતું કે અહીં કોઈ વાસ્તવિક ભૂલ છે. માત્ર એક બિનજરૂરી પુનરાવર્તિત સોંપણી. પરંતુ હજુ પણ એક ભૂલ.

તેવી જ રીતે:

  • V519 [CWE-563] 'B.NDesc' વેરીએબલને અનુક્રમે બે વાર મૂલ્યો અસાઇન કરવામાં આવે છે. કદાચ આ એક ભૂલ છે. રેખાઓ તપાસો: 1488, 1489. llvm-nm.cpp 1489
  • V519 [CWE-563] વેરીએબલને અનુક્રમે બે વાર મૂલ્યો અસાઇન કરવામાં આવે છે. કદાચ આ એક ભૂલ છે. રેખાઓ તપાસો: 59, 61. coff2yaml.cpp 61

ફ્રેગમેન્ટ N25-N27: વધુ ફરીથી સોંપણીઓ

હવે ચાલો ફરીથી સોંપણીની થોડી અલગ આવૃત્તિ જોઈએ.

bool Vectorizer::vectorizeLoadChain(
    ArrayRef<Instruction *> Chain,
    SmallPtrSet<Instruction *, 16> *InstructionsProcessed) {
  ....
  unsigned Alignment = getAlignment(L0);
  ....
  unsigned NewAlign = getOrEnforceKnownAlignment(L0->getPointerOperand(),
                                                 StackAdjustedAlignment,
                                                 DL, L0, nullptr, &DT);
  if (NewAlign != 0)
    Alignment = NewAlign;
  Alignment = NewAlign;
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V519 [CWE-563] 'સંરેખણ' વેરીએબલને અનુક્રમે બે વાર મૂલ્યો સોંપવામાં આવે છે. કદાચ આ એક ભૂલ છે. રેખાઓ તપાસો: 1158, 1160. LoadStoreVectorizer.cpp 1160

આ ખૂબ જ વિચિત્ર કોડ છે જે દેખીતી રીતે લોજિકલ ભૂલ ધરાવે છે. શરૂઆતમાં, ચલ ગોઠવણી સ્થિતિના આધારે મૂલ્ય અસાઇન કરવામાં આવે છે. અને પછી સોંપણી ફરીથી થાય છે, પરંતુ હવે કોઈપણ તપાસ વિના.

સમાન પરિસ્થિતિઓ અહીં જોઈ શકાય છે:

  • V519 [CWE-563] 'ઇફેક્ટ્સ' વેરીએબલને અનુક્રમે બે વાર મૂલ્યો સોંપવામાં આવે છે. કદાચ આ એક ભૂલ છે. રેખાઓ તપાસો: 152, 165. WebAssemblyRegStackify.cpp 165
  • V519 [CWE-563] 'ExpectNoDerefChunk' વેરીએબલને અનુક્રમે બે વાર મૂલ્યો સોંપવામાં આવે છે. કદાચ આ એક ભૂલ છે. રેખાઓ તપાસો: 4970, 4973. SemaType.cpp 4973

ફ્રેગમેન્ટ N28: હંમેશા સાચી સ્થિતિ

static int readPrefixes(struct InternalInstruction* insn) {
  ....
  uint8_t byte = 0;
  uint8_t nextByte;
  ....
  if (byte == 0xf3 && (nextByte == 0x88 || nextByte == 0x89 ||
                       nextByte == 0xc6 || nextByte == 0xc7)) {
    insn->xAcquireRelease = true;
    if (nextByte != 0x90) // PAUSE instruction support             // <=
      break;
  }
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V547 [CWE-571] અભિવ્યક્તિ 'nextByte != 0x90' હંમેશા સાચી હોય છે. X86DisassemblerDecoder.cpp 379

તપાસનો કોઈ અર્થ નથી. ચલ NextByte હંમેશા મૂલ્ય સમાન નથી 0x90, જે અગાઉના ચેકથી અનુસરે છે. આ એક પ્રકારની તાર્કિક ભૂલ છે.

ફ્રેગમેન્ટ N29 - N...: હંમેશા સાચી/ખોટી શરતો

વિશ્લેષક ઘણી ચેતવણીઓ આપે છે કે સમગ્ર સ્થિતિ (V547) અથવા તેનો ભાગ (V560) હંમેશા સાચું કે ખોટું હોય છે. મોટેભાગે આ વાસ્તવિક ભૂલો નથી, પરંતુ ફક્ત ઢાળવાળી કોડ, મેક્રો વિસ્તરણનું પરિણામ અને તેના જેવા. જો કે, આ બધી ચેતવણીઓને જોવાનો અર્થ થાય છે, કારણ કે સમયાંતરે વાસ્તવિક તાર્કિક ભૂલો થાય છે. ઉદાહરણ તરીકે, કોડનો આ વિભાગ શંકાસ્પદ છે:

static DecodeStatus DecodeGPRPairRegisterClass(MCInst &Inst, unsigned RegNo,
                                   uint64_t Address, const void *Decoder) {
  DecodeStatus S = MCDisassembler::Success;

  if (RegNo > 13)
    return MCDisassembler::Fail;

  if ((RegNo & 1) || RegNo == 0xe)
     S = MCDisassembler::SoftFail;
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V560 [CWE-570] શરતી અભિવ્યક્તિનો એક ભાગ હંમેશા ખોટો હોય છે: RegNo == 0xe. ARMDisassembler.cpp 939

સ્થિર 0xE એ દશાંશમાં 14 મૂલ્ય છે. પરીક્ષા RegNo == 0xe અર્થ નથી કારણ કે જો RegNo > 13, પછી ફંક્શન તેનો અમલ પૂર્ણ કરશે.

IDs V547 અને V560 સાથે અન્ય ઘણી ચેતવણીઓ હતી, પરંતુ તેની સાથે V595, મને આ ચેતવણીઓનો અભ્યાસ કરવામાં રસ નહોતો. તે પહેલેથી જ સ્પષ્ટ હતું કે મારી પાસે લેખ લખવા માટે પૂરતી સામગ્રી છે :). તેથી, PVS-Studio નો ઉપયોગ કરીને LLVM માં આ પ્રકારની કેટલી ભૂલો ઓળખી શકાય તે અજ્ઞાત છે.

હું તમને એક ઉદાહરણ આપીશ કે શા માટે આ ટ્રિગર્સનો અભ્યાસ કરવો કંટાળાજનક છે. નીચેના કોડ માટે ચેતવણી જારી કરવામાં વિશ્લેષક એકદમ યોગ્ય છે. પરંતુ આ કોઈ ભૂલ નથી.

bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons,
                                          tok::TokenKind ClosingBraceKind) {
  bool HasError = false;
  ....
  HasError = true;
  if (!ContinueOnSemicolons)
    return !HasError;
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V547 [CWE-570] અભિવ્યક્તિ '!HasError' હંમેશા ખોટી હોય છે. UnwrappedLineParser.cpp 1635

ફ્રેગમેન્ટ N30: ​​શંકાસ્પદ વળતર

static bool
isImplicitlyDef(MachineRegisterInfo &MRI, unsigned Reg) {
  for (MachineRegisterInfo::def_instr_iterator It = MRI.def_instr_begin(Reg),
      E = MRI.def_instr_end(); It != E; ++It) {
    return (*It).isImplicitDef();
  }
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V612 [CWE-670] લૂપની અંદર બિનશરતી 'રીટર્ન'. R600OptimizeVectorRegisters.cpp 63

આ કાં તો ભૂલ છે અથવા ચોક્કસ ટેકનિક છે જેનો હેતુ કોડ વાંચતા પ્રોગ્રામરોને કંઈક સમજાવવાનો છે. આ ડિઝાઇન મને કંઈપણ સમજાવતી નથી અને ખૂબ જ શંકાસ્પદ લાગે છે. એવું ન લખવું સારું :).

થાકી ગયા છો? પછી ચા કે કોફી બનાવવાનો સમય છે.

PVS-સ્ટુડિયો વિશ્લેષકનો ઉપયોગ કરીને LLVM 8 માં ભૂલો શોધવી

નવા ડાયગ્નોસ્ટિક્સ દ્વારા ઓળખવામાં આવેલી ખામીઓ

મને લાગે છે કે જૂના ડાયગ્નોસ્ટિક્સના 30 સક્રિયકરણો પર્યાપ્ત છે. ચાલો હવે જોઈએ કે નવા ડાયગ્નોસ્ટિક્સ સાથે કઈ રસપ્રદ વસ્તુઓ મળી શકે છે જે વિશ્લેષકમાં પછી દેખાય છે અગાઉના ચેક આ સમય દરમિયાન, C++ વિશ્લેષકમાં કુલ 66 સામાન્ય-ઉદ્દેશ ડાયગ્નોસ્ટિક્સ ઉમેરવામાં આવ્યા હતા.

ફ્રેગમેન્ટ N31: અગમ્ય કોડ

Error CtorDtorRunner::run() {
  ....
  if (auto CtorDtorMap =
          ES.lookup(JITDylibSearchList({{&JD, true}}), std::move(Names),
                    NoDependenciesToRegister, true))
  {
    ....
    return Error::success();
  } else
    return CtorDtorMap.takeError();

  CtorDtorsByPriority.clear();

  return Error::success();
}

PVS-સ્ટુડિયો ચેતવણી: V779 [CWE-561] અગમ્ય કોડ મળ્યો. શક્ય છે કે કોઈ ભૂલ હાજર હોય. ExecutionUtils.cpp 146

જેમ તમે જોઈ શકો છો, ઓપરેટરની બંને શાખાઓ if ઑપરેટરને કૉલ સાથે સમાપ્ત થાય છે વળતર. તદનુસાર, કન્ટેનર CtorDtorsBypriority ક્યારેય સાફ થશે નહીં.

ફ્રેગમેન્ટ N32: અગમ્ય કોડ

bool LLParser::ParseSummaryEntry() {
  ....
  switch (Lex.getKind()) {
  case lltok::kw_gv:
    return ParseGVEntry(SummaryID);
  case lltok::kw_module:
    return ParseModuleEntry(SummaryID);
  case lltok::kw_typeid:
    return ParseTypeIdEntry(SummaryID);                        // <=
    break;                                                     // <=
  default:
    return Error(Lex.getLoc(), "unexpected summary kind");
  }
  Lex.setIgnoreColonInIdentifiers(false);                      // <=
  return false;
}

PVS-સ્ટુડિયો ચેતવણી: V779 [CWE-561] પહોંચી ન શકાય એવો કોડ મળ્યો. શક્ય છે કે કોઈ ભૂલ હાજર હોય. LLParser.cpp 835

રસપ્રદ પરિસ્થિતિ. ચાલો પહેલા આ સ્થાન જોઈએ:

return ParseTypeIdEntry(SummaryID);
break;

પ્રથમ નજરમાં, એવું લાગે છે કે અહીં કોઈ ભૂલ નથી. તે ઓપરેટર જેવું લાગે છે વિરામ અહીં એક વધારાનું છે, અને તમે તેને ખાલી કાઢી શકો છો. જો કે, બધું એટલું સરળ નથી.

વિશ્લેષક લીટીઓ પર ચેતવણી આપે છે:

Lex.setIgnoreColonInIdentifiers(false);
return false;

અને ખરેખર, આ કોડ અગમ્ય છે. માં તમામ કેસ સ્વીચ ઑપરેટરના કૉલ સાથે સમાપ્ત થાય છે વળતર. અને હવે એકલા અણસમજુ વિરામ એટલું હાનિકારક નથી લાગતું! કદાચ શાખાઓમાંની એક સાથે સમાપ્ત થવી જોઈએ વિરામ, ચાલુ નથી વળતર?

ફ્રેગમેન્ટ N33: ઉચ્ચ બિટ્સનું રેન્ડમ રીસેટ

unsigned getStubAlignment() override {
  if (Arch == Triple::systemz)
    return 8;
  else
    return 1;
}

Expected<unsigned>
RuntimeDyldImpl::emitSection(const ObjectFile &Obj,
                             const SectionRef &Section,
                             bool IsCode) {
  ....
  uint64_t DataSize = Section.getSize();
  ....
  if (StubBufSize > 0)
    DataSize &= ~(getStubAlignment() - 1);
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V784 બીટ માસ્કનું કદ પ્રથમ ઓપરેન્ડના કદ કરતા ઓછું છે. આ ઉચ્ચ બિટ્સના નુકશાનનું કારણ બનશે. RuntimeDyld.cpp 815

મહેરબાની કરીને નોંધ કરો કે કાર્ય getStubAlignment વળતર પ્રકાર સહી વિનાનું. ચાલો અભિવ્યક્તિના મૂલ્યની ગણતરી કરીએ, એમ ધારીને કે કાર્ય મૂલ્ય 8 આપે છે:

~(getStubAlignment() - 1)

~(8u-1)

0xFFFFFFFF8u

હવે નોંધ લો કે ચલ ડેટાસાઇઝ 64-બીટ સહી વગરનો પ્રકાર ધરાવે છે. તે તારણ આપે છે કે ડેટાસાઇઝ અને 0xFFFFFFF8u ઑપરેશન કરતી વખતે, તમામ બત્રીસ હાઇ-ઑર્ડર બિટ્સ શૂન્ય પર ફરીથી સેટ કરવામાં આવશે. મોટે ભાગે, આ તે નથી જે પ્રોગ્રામર ઇચ્છે છે. મને શંકા છે કે તે ગણતરી કરવા માંગતો હતો: DataSize & 0xFFFFFFFFFFFFFF8u.

ભૂલને ઠીક કરવા માટે, તમારે આ લખવું જોઈએ:

DataSize &= ~(static_cast<uint64_t>(getStubAlignment()) - 1);

અથવા તેથી:

DataSize &= ~(getStubAlignment() - 1ULL);

ફ્રેગમેન્ટ N34: સ્પષ્ટ પ્રકારનું કાસ્ટ નિષ્ફળ થયું

template <typename T>
void scaleShuffleMask(int Scale, ArrayRef<T> Mask,
                      SmallVectorImpl<T> &ScaledMask) {
  assert(0 < Scale && "Unexpected scaling factor");
  int NumElts = Mask.size();
  ScaledMask.assign(static_cast<size_t>(NumElts * Scale), -1);
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V1028 [CWE-190] શક્ય ઓવરફ્લો. 'NumElts * Scale' ઓપરેટરના કાસ્ટિંગ ઓપરેન્ડને 'size_t' પ્રકારમાં ધ્યાનમાં લો, પરિણામ નહીં. X86ISelLowering.h 1577

સ્પષ્ટ પ્રકાર કાસ્ટિંગનો ઉપયોગ જ્યારે પ્રકાર વેરિયેબલનો ગુણાકાર કરતી વખતે ઓવરફ્લો ટાળવા માટે થાય છે પૂર્ણાંક. જો કે, અહીં સ્પષ્ટ પ્રકારનું કાસ્ટિંગ ઓવરફ્લો સામે રક્ષણ આપતું નથી. પ્રથમ, ચલોનો ગુણાકાર કરવામાં આવશે, અને માત્ર ત્યારે જ ગુણાકારના 32-બીટ પરિણામને પ્રકારમાં વિસ્તૃત કરવામાં આવશે. માપ_ટી.

ફ્રેગમેન્ટ N35: કોપી-પેસ્ટ નિષ્ફળ

Instruction *InstCombiner::visitFCmpInst(FCmpInst &I) {
  ....
  if (!match(Op0, m_PosZeroFP()) && isKnownNeverNaN(Op0, &TLI)) {
    I.setOperand(0, ConstantFP::getNullValue(Op0->getType()));
    return &I;
  }
  if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
    I.setOperand(1, ConstantFP::getNullValue(Op0->getType()));        // <=
    return &I;
  }
  ....
}

V778 [CWE-682] બે સમાન કોડ ટુકડાઓ મળી આવ્યા હતા. કદાચ, આ એક ટાઈપો છે અને 'Op1' ને બદલે 'Op0' ચલનો ઉપયોગ કરવો જોઈએ. InstCombineCompares.cpp 5507

આ નવું રસપ્રદ ડાયગ્નોસ્ટિક એવી પરિસ્થિતિઓને ઓળખે છે જ્યાં કોડના ટુકડાની નકલ કરવામાં આવી છે અને તેમાંના કેટલાક નામ બદલવાનું શરૂ થયું છે, પરંતુ એક જગ્યાએ તેઓએ તેને સુધાર્યું નથી.

મહેરબાની કરીને નોંધ કરો કે બીજા બ્લોકમાં તેઓ બદલાયા છે ઓપ0 પર ઓપ1. પરંતુ એક જગ્યાએ તેઓએ તેને ઠીક કર્યું નથી. મોટે ભાગે તે આ રીતે લખાયેલ હોવું જોઈએ:

if (!match(Op1, m_PosZeroFP()) && isKnownNeverNaN(Op1, &TLI)) {
  I.setOperand(1, ConstantFP::getNullValue(Op1->getType()));
  return &I;
}

ફ્રેગમેન્ટ N36: ચલ મૂંઝવણ

struct Status {
  unsigned Mask;
  unsigned Mode;

  Status() : Mask(0), Mode(0){};

  Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
    Mode &= Mask;
  };
  ....
};

PVS-સ્ટુડિયો ચેતવણી: V1001 [CWE-563] 'મોડ' વેરીએબલ અસાઇન કરવામાં આવે છે પરંતુ ફંક્શનના અંત સુધીમાં તેનો ઉપયોગ થતો નથી. SIModeRegister.cpp 48

ફંક્શન દલીલોને વર્ગના સભ્યો જેવા જ નામ આપવાનું ખૂબ જોખમી છે. મૂંઝવણમાં આવવું ખૂબ જ સરળ છે. અમારી સામે આવો જ એક કિસ્સો છે. આ અભિવ્યક્તિનો કોઈ અર્થ નથી:

Mode &= Mask;

કાર્ય દલીલ બદલાય છે. બસ એટલું જ. આ દલીલ હવે ઉપયોગમાં લેવાતી નથી. મોટે ભાગે તમારે તેને આના જેવું લખવું જોઈએ:

Status(unsigned Mask, unsigned Mode) : Mask(Mask), Mode(Mode) {
  this->Mode &= Mask;
};

ફ્રેગમેન્ટ N37: ચલ મૂંઝવણ

class SectionBase {
  ....
  uint64_t Size = 0;
  ....
};

class SymbolTableSection : public SectionBase {
  ....
};

void SymbolTableSection::addSymbol(Twine Name, uint8_t Bind, uint8_t Type,
                                   SectionBase *DefinedIn, uint64_t Value,
                                   uint8_t Visibility, uint16_t Shndx,
                                   uint64_t Size) {
  ....
  Sym.Value = Value;
  Sym.Visibility = Visibility;
  Sym.Size = Size;
  Sym.Index = Symbols.size();
  Symbols.emplace_back(llvm::make_unique<Symbol>(Sym));
  Size += this->EntrySize;
}

ચેતવણી PVS-Studio: V1001 [CWE-563] 'સાઇઝ' ચલ અસાઇન કરવામાં આવે છે પરંતુ ફંક્શનના અંત સુધીમાં તેનો ઉપયોગ થતો નથી. Object.cpp 424

પરિસ્થિતિ અગાઉના એક જેવી જ છે. તે લખવું જોઈએ:

this->Size += this->EntrySize;

ફ્રેગમેન્ટ N38-N47: તેઓ ઇન્ડેક્સ તપાસવાનું ભૂલી ગયા

અગાઉ, અમે ડાયગ્નોસ્ટિક ટ્રિગરિંગના ઉદાહરણો જોયા હતા V595. તેનો સાર એ છે કે નિર્દેશકને શરૂઆતમાં ડિરેફરન્સ કરવામાં આવે છે, અને તે પછી જ તપાસવામાં આવે છે. યુવાન ડાયગ્નોસ્ટિક્સ V1004 અર્થમાં વિપરીત છે, પરંતુ ઘણી બધી ભૂલો પણ દર્શાવે છે. તે એવી પરિસ્થિતિઓને ઓળખે છે જ્યાં નિર્દેશકને શરૂઆતમાં તપાસવામાં આવ્યું હતું અને પછી તે કરવાનું ભૂલી ગયા હતા. ચાલો LLVM ની અંદર જોવા મળતા આવા કિસ્સાઓ જોઈએ.

int getGEPCost(Type *PointeeType, const Value *Ptr,
               ArrayRef<const Value *> Operands) {
  ....
  if (Ptr != nullptr) {                                            // <=
    assert(....);
    BaseGV = dyn_cast<GlobalValue>(Ptr->stripPointerCasts());
  }
  bool HasBaseReg = (BaseGV == nullptr);

  auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());  // <=
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V1004 [CWE-476] 'Ptr' પોઇન્ટરનો ઉપયોગ અસુરક્ષિત રીતે કરવામાં આવ્યો હતો પછી તે nullptr સામે ચકાસવામાં આવ્યો હતો. રેખાઓ તપાસો: 729, 738. TargetTransformInfoImpl.h 738

ચલ Ptr સમાન હોઈ શકે છે nullptr, ચેક દ્વારા પુરાવા મુજબ:

if (Ptr != nullptr)

જો કે, આ નિર્દેશકની નીચે પ્રાથમિક તપાસ કર્યા વિના અવગણવામાં આવે છે:

auto PtrSizeBits = DL.getPointerTypeSizeInBits(Ptr->getType());

ચાલો બીજા સમાન કેસને ધ્યાનમાં લઈએ.

llvm::DISubprogram *CGDebugInfo::getFunctionFwdDeclOrStub(GlobalDecl GD,
                                                          bool Stub) {
  ....
  auto *FD = dyn_cast<FunctionDecl>(GD.getDecl());
  SmallVector<QualType, 16> ArgTypes;
  if (FD)                                                                // <=
    for (const ParmVarDecl *Parm : FD->parameters())
      ArgTypes.push_back(Parm->getType());
  CallingConv CC = FD->getType()->castAs<FunctionType>()->getCallConv(); // <=
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V1004 [CWE-476] 'FD' પોઇન્ટરનો ઉપયોગ અસુરક્ષિત રીતે કરવામાં આવ્યો હતો પછી તે nullptr સામે ચકાસવામાં આવ્યો હતો. રેખાઓ તપાસો: 3228, 3231. CGDebugInfo.cpp 3231

નિશાની પર ધ્યાન આપો FD. મને ખાતરી છે કે સમસ્યા સ્પષ્ટપણે દેખાઈ રહી છે અને કોઈ વિશેષ સમજૂતીની જરૂર નથી.

અને આગળ:

static void computePolynomialFromPointer(Value &Ptr, Polynomial &Result,
                                         Value *&BasePtr,
                                         const DataLayout &DL) {
  PointerType *PtrTy = dyn_cast<PointerType>(Ptr.getType());
  if (!PtrTy) {                                                   // <=
    Result = Polynomial();
    BasePtr = nullptr;
  }
  unsigned PointerBits =
      DL.getIndexSizeInBits(PtrTy->getPointerAddressSpace());     // <=
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V1004 [CWE-476] 'PtrTy' પોઇન્ટરનો ઉપયોગ અસુરક્ષિત રીતે કરવામાં આવ્યો હતો પછી તે nullptr સામે ચકાસવામાં આવ્યો હતો. રેખાઓ તપાસો: 960, 965. InterleavedLoadCombinePass.cpp 965

આવી ભૂલોથી તમારી જાતને કેવી રીતે બચાવવી? કોડ-રિવ્યુ પર વધુ સચેત બનો અને તમારા કોડને નિયમિતપણે તપાસવા માટે PVS-Studio સ્ટેટિક વિશ્લેષકનો ઉપયોગ કરો.

આ પ્રકારની ભૂલો સાથે અન્ય કોડ ટુકડાઓ ટાંકવાનો કોઈ અર્થ નથી. હું લેખમાં ફક્ત ચેતવણીઓની સૂચિ છોડીશ:

  • V1004 [CWE-476] nullptr સામે ચકાસવામાં આવ્યા પછી 'Expr' પોઇન્ટરનો અસુરક્ષિત ઉપયોગ કરવામાં આવ્યો હતો. રેખાઓ તપાસો: 1049, 1078. DebugInfoMetadata.cpp 1078
  • V1004 [CWE-476] 'PI' પોઇન્ટર nullptr સામે ચકાસવામાં આવ્યા પછી તેનો અસુરક્ષિત ઉપયોગ કરવામાં આવ્યો હતો. લીટીઓ તપાસો: 733, 753. LegacyPassManager.cpp 753
  • V1004 [CWE-476] 'સ્ટેટપોઈન્ટકૉલ' પોઈન્ટરનો ઉપયોગ અસુરક્ષિત રીતે કરવામાં આવ્યો હતો પછી તે nullptr સામે ચકાસવામાં આવ્યો હતો. રેખાઓ તપાસો: 4371, 4379. Verifier.cpp 4379
  • V1004 [CWE-476] 'RV' પોઇન્ટર nullptr સામે ચકાસવામાં આવ્યા પછી તેનો અસુરક્ષિત ઉપયોગ કરવામાં આવ્યો હતો. રેખાઓ તપાસો: 2263, 2268. TGParser.cpp 2268
  • V1004 [CWE-476] 'CalleeFn' પોઇન્ટર nullptr સામે ચકાસવામાં આવ્યા પછી તેનો ઉપયોગ અસુરક્ષિત રીતે કરવામાં આવ્યો હતો. રેખાઓ તપાસો: 1081, 1096. SimplifyLibCalls.cpp 1096
  • V1004 [CWE-476] nullptr સામે ચકાસવામાં આવ્યા પછી 'TC' પોઇન્ટરનો અસુરક્ષિત ઉપયોગ કરવામાં આવ્યો હતો. રેખાઓ તપાસો: 1819, 1824. ડ્રાઈવર.cpp 1824

ફ્રેગમેન્ટ N48-N60: ગંભીર નથી, પરંતુ ખામી (સંભવિત મેમરી લીક)

std::unique_ptr<IRMutator> createISelMutator() {
  ....
  std::vector<std::unique_ptr<IRMutationStrategy>> Strategies;
  Strategies.emplace_back(
      new InjectorIRStrategy(InjectorIRStrategy::getDefaultOps()));
  ....
}

PVS-સ્ટુડિયો ચેતવણી: V1023 [CWE-460] 'એમ્પ્લેસ_બેક' પદ્ધતિ દ્વારા 'સ્ટ્રેટેજીઝ' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. llvm-isel-fuzzer.cpp 58

જેમ કે કન્ટેનરના અંતમાં એક તત્વ ઉમેરવા માટે std::વેક્ટર > તમે ફક્ત લખી શકતા નથી xxx.push_back(નવું X), કારણ કે ત્યાંથી કોઈ ગર્ભિત રૂપાંતર નથી X* в std::unique_ptr.

એક સામાન્ય ઉકેલ લખવાનો છે xxx.emplace_back(નવું X)કારણ કે તે કમ્પાઇલ કરે છે: પદ્ધતિ emplace_back દલીલોમાંથી સીધા જ તત્વનું નિર્માણ કરે છે અને તેથી સ્પષ્ટ કન્સ્ટ્રક્ટરનો ઉપયોગ કરી શકે છે.

તે સુરક્ષિત નથી. જો વેક્ટર ભરેલું હોય, તો મેમરી ફરીથી ફાળવવામાં આવે છે. મેમરી રીલોકેશન ઓપરેશન નિષ્ફળ થઈ શકે છે, પરિણામે અપવાદ ફેંકવામાં આવે છે std::bad_alloc. આ કિસ્સામાં, નિર્દેશક ખોવાઈ જશે અને બનાવેલ ઑબ્જેક્ટ ક્યારેય કાઢી નાખવામાં આવશે નહીં.

એક સુરક્ષિત ઉકેલ બનાવવા માટે છે અનન્ય_ptrવેક્ટર મેમરીને ફરીથી ફાળવવાનો પ્રયાસ કરે તે પહેલાં જે પોઇન્ટરની માલિકી ધરાવશે:

xxx.push_back(std::unique_ptr<X>(new X))

C++14 થી, તમે 'std::make_unique' નો ઉપયોગ કરી શકો છો:

xxx.push_back(std::make_unique<X>())

LLVM માટે આ પ્રકારની ખામી ગંભીર નથી. જો મેમરી ફાળવી શકાતી નથી, તો કમ્પાઈલર ફક્ત બંધ થઈ જશે. જો કે, લાંબા સાથે અરજીઓ માટે અપટાઇમ, જે ફક્ત મેમરી ફાળવણી નિષ્ફળ જાય તો સમાપ્ત કરી શકાતી નથી, આ એક વાસ્તવિક બીભત્સ બગ હોઈ શકે છે.

તેથી, જો કે આ કોડ LLVM માટે વ્યવહારુ ખતરો ઉભો કરતો નથી, મને આ ભૂલ પેટર્ન વિશે વાત કરવી ઉપયોગી લાગી અને PVS-સ્ટુડિયો વિશ્લેષકે તેને ઓળખવાનું શીખી લીધું છે.

આ પ્રકારની અન્ય ચેતવણીઓ:

  • V1023 [CWE-460] 'એમ્પ્લેસ_બેક' પદ્ધતિ દ્વારા 'પાસેસ' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. PassManager.h 546
  • V1023 [CWE-460] 'એએએસ' કન્ટેનરમાં 'એમ્પ્લેસ_બેક' પદ્ધતિ દ્વારા માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. AliasAnalysis.h 324
  • V1023 [CWE-460] 'એન્ટ્રીઝ' કન્ટેનરમાં 'એમ્પ્લેસ_બેક' પદ્ધતિ દ્વારા માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. DWARFDebugFrame.cpp 519
  • V1023 [CWE-460] માલિક વિનાનું પોઈન્ટર 'એપ્લેસ_બેક' પદ્ધતિ દ્વારા 'AllEdges' કન્ટેનરમાં ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. CFGMST.h 268
  • V1023 [CWE-460] 'emplace_back' પદ્ધતિ દ્વારા 'VMaps' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. SimpleLoopUnswitch.cpp 2012
  • V1023 [CWE-460] 'એમ્પ્લેસ_બેક' પદ્ધતિ દ્વારા 'રેકોર્ડ્સ' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. FDRLogBuilder.h 30
  • V1023 [CWE-460] 'એમ્પ્લેસ_બેક' પદ્ધતિ દ્વારા 'પેન્ડિંગસબમોડ્યુલ્સ' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. ModuleMap.cpp 810
  • V1023 [CWE-460] માલિક વિનાનું પોઈન્ટર 'ઓબ્જેક્ટ્સ' કન્ટેનરમાં 'emplace_back' પદ્ધતિ દ્વારા ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. DebugMap.cpp 88
  • V1023 [CWE-460] 'એમ્પ્લેસ_બેક' પદ્ધતિ દ્વારા 'સ્ટ્રેટેજીઝ' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. llvm-isel-fuzzer.cpp 60
  • V1023 [CWE-460] 'emplace_back' પદ્ધતિ દ્વારા 'Modifiers' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. llvm-stress.cpp 685
  • V1023 [CWE-460] 'emplace_back' પદ્ધતિ દ્વારા 'Modifiers' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. llvm-stress.cpp 686
  • V1023 [CWE-460] 'emplace_back' પદ્ધતિ દ્વારા 'Modifiers' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. llvm-stress.cpp 688
  • V1023 [CWE-460] 'emplace_back' પદ્ધતિ દ્વારા 'Modifiers' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. llvm-stress.cpp 689
  • V1023 [CWE-460] 'emplace_back' પદ્ધતિ દ્વારા 'Modifiers' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. llvm-stress.cpp 690
  • V1023 [CWE-460] 'emplace_back' પદ્ધતિ દ્વારા 'Modifiers' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. llvm-stress.cpp 691
  • V1023 [CWE-460] 'emplace_back' પદ્ધતિ દ્વારા 'Modifiers' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. llvm-stress.cpp 692
  • V1023 [CWE-460] 'emplace_back' પદ્ધતિ દ્વારા 'Modifiers' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. llvm-stress.cpp 693
  • V1023 [CWE-460] 'emplace_back' પદ્ધતિ દ્વારા 'Modifiers' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. llvm-stress.cpp 694
  • V1023 [CWE-460] 'એમ્પ્લેસ_બેક' પદ્ધતિ દ્વારા 'ઓપરેન્ડ્સ' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. GlobalISelEmitter.cpp 1911
  • V1023 [CWE-460] 'એમ્પ્લેસ_બેક' પદ્ધતિ દ્વારા 'સ્ટેશ' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. GlobalISelEmitter.cpp 2100
  • V1023 [CWE-460] 'એમ્પ્લેસ_બેક' પદ્ધતિ દ્વારા 'મેચર્સ' કન્ટેનરમાં માલિક વિનાનું પોઈન્ટર ઉમેરવામાં આવે છે. અપવાદના કિસ્સામાં મેમરી લીક થશે. GlobalISelEmitter.cpp 2702

નિષ્કર્ષ

મેં કુલ 60 ચેતવણીઓ જારી કરી અને પછી બંધ કરી. શું PVS-Studio વિશ્લેષક LLVM માં શોધી કાઢે તેવી અન્ય ખામીઓ છે? હા, મારી પાસે છે. જો કે, જ્યારે હું લેખ માટે કોડ ટુકડાઓ લખી રહ્યો હતો, ત્યારે તે મોડી સાંજ હતી, અથવા તો રાત પણ હતી, અને મેં નક્કી કર્યું કે તેને એક દિવસ કહેવાનો સમય છે.

હું આશા રાખું છું કે તમને તે રસપ્રદ લાગ્યું અને PVS-સ્ટુડિયો વિશ્લેષકને અજમાવવા માંગશો.

તમે વિશ્લેષક ડાઉનલોડ કરી શકો છો અને માઈનસ્વીપર કી મેળવી શકો છો આ પૃષ્ઠ.

સૌથી અગત્યનું, નિયમિતપણે સ્થિર વિશ્લેષણનો ઉપયોગ કરો. એક વખતની તપાસ, સ્થિર વિશ્લેષણની પદ્ધતિને લોકપ્રિય બનાવવા માટે અમારા દ્વારા હાથ ધરવામાં આવે છે અને PVS-સ્ટુડિયો સામાન્ય દૃશ્ય નથી.

તમારા કોડની ગુણવત્તા અને વિશ્વસનીયતા સુધારવા માટે સારા નસીબ!

PVS-સ્ટુડિયો વિશ્લેષકનો ઉપયોગ કરીને LLVM 8 માં ભૂલો શોધવી

જો તમે આ લેખ અંગ્રેજી બોલતા પ્રેક્ષકો સાથે શેર કરવા માંગતા હો, તો કૃપા કરીને અનુવાદ લિંકનો ઉપયોગ કરો: એન્ડ્રી કાર્પોવ. PVS-Studio સાથે LLVM 8 માં બગ્સ શોધવી.

સોર્સ: www.habr.com

એક ટિપ્પણી ઉમેરો