فحص محلل PVS-Studio RunUO

الصورة 1

هذه المقالة مخصصة للتحقق من مشروع RunUO باستخدام محلل ثابت PVS-Studio. RunUO هو محاكي برنامج خادم لـ Ultima Online ، لعبة فازت بقلوب العديد من محبي لعبة MMORPG.

المقدمة


RunUO هو محاكي برنامج خادم لـ MMORPG Ultima Online. الهدف من هذا المشروع هو إنشاء برامج مستقرة قادرة على المنافسة مع الخوادم الرسمية لألعاب EA. تم إنشاء RunUO في عام 2002 ، لكنه لا يفقد أهميته ويستخدم بنشاط حتى يومنا هذا.

الغرض من التحقق من المشروع هو تعميم موضوع التحليل الثابت. نتحقق من مشاريع مختلفة - ألعاب ( مثال ) ، مكتبات ( مثال ) ، رسل ( مثال ) ، متصفحات ( مثال ) وأكثر من ذلك بكثير ( مثال ، مثال ، مثال) لجذب انتباه جمهور متنوع. مع هذه المقالات ، نحاول لفت الانتباه إلى أهمية استخدام التحليل الثابت في التنمية. التحليل الثابت يجعل الشفرة أكثر موثوقية وأكثر أمانًا. أيضًا ، باستخدامه المنتظم ، يمكنك العثور على الأخطاء وإزالتها في المراحل الأولى. وهذا يوفر الوقت والجهد للمطورين ، لأنه لا أحد يرغب في قضاء 50 ساعة في البحث عن الخطأ الذي يمكن أن يجده المحلل.

نساعد أيضًا مجتمع المصادر المفتوحة. من خلال المقالات التي تحتوي على أخطاء ، نساهم في تطوير المصادر المفتوحة. ومع ذلك ، في المقالات لا نقوم بتحليل جميع التحذيرات. بدت بعض التحذيرات مملة للغاية بالنسبة لنا للدخول إلى المقالة ، وتبين أن بعض الإيجابيات الزائفة ، إلخ. لذلك ، نحن على استعداد لتقديم ترخيص مجاني لمشاريع مفتوحة المصدر. علاوة على ذلك ، ما يبدو لنا مملاً للمقال قد يبدو مثيرًا للاهتمام للغاية لمطوري المشروع الذي يتم اختباره ، لأن مطوري المشروع مع ذلك أكثر وعياً بالمشكلات الأكثر أهمية.

أجزاء من التعليمات البرمجية التي لفتت الانتباه عند فحص تقرير المحلل


تحذير PVS-Studio: V3010 مطلوب استخدام القيمة المرجعة للدالة "Intern". BasePaintedMask.cs 49

public static string Intern( string str )
{
  if ( str == null )
    return null;
  else if ( str.Length == 0 )
    return String.Empty;

  return String.Intern( str );
}

public BasePaintedMask( string staffer, int itemid )
                            : base( itemid + Utility.Random( 2 ) )
{
  m_Staffer = staffer;

  Utility.Intern( m_Staffer );
}

لا يتم أخذ القيمة المرتجعة لطريقة Intern () في الاعتبار في أي مكان ، كما هو موضح من قبل المحلل. ربما يكون هذا خطأ أو رمز زائدة.

تحذير PVS-Studio: V3017 تم اكتشاف نمط: (العنصر هو BasePotion) || ((العنصر هو BasePotion) && ...). التعبير مفرط أو يحتوي على خطأ منطقي. الحلقة 137

public static bool IsBuggable( Item item )
{
  if ( item is Fists )
    return false;

  if ( item is ICommodity || item is Multis.BaseBoat
    || item is Fish || item is BigFish
    || item is BasePotion || item is Food || item is CookableFood
    || item is SpecialFishingNet || item is BaseMagicFish
    || item is Shoes || item is Sandals
    || item is Boots || item is ThighBoots
    || item is TreasureMap || item is MessageInABottle
    || item is BaseArmor || item is BaseWeapon
    || item is BaseClothing
    || ( item is BaseJewel && Core.AOS )
    || ( item is BasePotion && Core.ML )
  {
    ....
  }
}

هناك تعبيرات فرعية يمكن تبسيطها. سأكتبها بحيث تكون أكثر وضوحًا:
if (item is BasePotion || ( item is BasePotion && Core.ML ))

لنفترض، البند هو BasePotion = إلى true ، ثم كان الشرط صحيحا، على الرغم من Core.ML . إذا كان البند هو BasePotion = إلى false ، ثم حالة غير صحيحة، ومرة أخرى بغض النظر عن القيمة Core.ML . غالبًا ما تكون هذه الشفرة زائدة عن الحاجة ، ومع ذلك ، هناك حالات أسوأ عندما يرتكب المبرمج خطأ وكتب المتغير الخاطئ في التعبير الفرعي الثاني.

تحذير PVS-Studio: V3031 يمكن تبسيط الفحص الزائد. '||' عامل التشغيل محاط بالتعبيرات المعاكسة "bPlayerOnly" و "! bPlayerOnly". BaseCreature.cs 3005

public virtual double GetFightModeRanking( Mobile m,
                                           FightMode acqType,
                                           bool bPlayerOnly )
{
  if ( ( bPlayerOnly && m.Player ) ||  !bPlayerOnly )
  {
    ....
  }
  ....
}

هذا الرمز إما زائدة أو خاطئة. مشكلته هي أنه على جانبي "||" تختلف بمعنى التعبير الفرعي. إذا قطعناها على هذا النحو:

if ( m.Player || !bPlayerOnly )

عندها لن يتغير شيء.

تحذير PVS-Studio: V3001 هناك عبارات متطابقة متطابقة "الفعل هو SmallBrickHouseDeed" إلى اليسار وإلى اليمين من "||" المشغل أو العامل. 132

public int ComputePriceFor( HouseDeed deed )
{
  int price = 0;

  if ( deed is SmallBrickHouseDeed ||    // <=
       deed is StonePlasterHouseDeed ||
       deed is FieldStoneHouseDeed ||
       deed is SmallBrickHouseDeed ||    // <=
       deed is WoodHouseDeed ||
       deed is WoodPlasterHouseDeed ||
       deed is ThatchedRoofCottageDeed )
      ....
}

أعتقد أنه ليس من المفيد شرح أي شيء هنا ، الرمز التالي أو الخطأ ، أو زائدة عن الحاجة.

تحذير PVS-Studio: V3067 من الممكن أن يتم نسيان كتلة "آخر" أو التعليق عليها ، وبالتالي تغيير منطق تشغيل البرنامج. الحلقة 1558

private void SetLockdown( Item i, bool locked, bool checkContains )
{
  if ( m_LockDowns == null )
    return;

  #region Mondain's Legacy
  if ( i is BaseAddonContainer )
    i.Movable = false;
  else
  #endregion

  i.Movable = !locked;
  i.IsLockedDown = locked;

  ....
}

تحذير نادر جدا. بدا المحلل مريبًا بشأن تنسيق الكود بعد توجيه #endregion. إذا لم تقرأ الرمز ، فسيبدو كخط

i.Movable = !locked;

سيتم تنفيذها في أي حال ، بغض النظر عن المتغير i . ربما تم نسيان الأقواس المتعرجة هنا ... بشكل عام ، يجب على المطورين التحقق من هذا الرمز.

تحذير PVS-Studio: V3043 لا يتوافق منطق تشغيل الكود مع تنسيقه. العبارة ذات مسافة بادئة إلى اليمين ، ولكن يتم تنفيذها دائمًا. من الممكن أن تكون الأقواس المتعرجة مفقودة. زلزال 57

public override void OnCast()
{
  if ( Core.AOS )
  {
    damage = m.Hits / 2;

    if ( !m.Player )
      damage = Math.Max( Math.Min( damage, 100 ), 15 );
      damage += Utility.RandomMinMax( 0, 15 );            // <=

  }
  else
  {
    ....
  }
}

قد تكون الأقواس المتعرجة مفقودة في هذا الرمز. يمكن أن يتم هذا الاستنتاج بسبب التنسيق الغريب للرمز في نص if (! M.Player) .

تحذير PVS-Studio: V3083 استدعاء غير آمن للحدث "ServerStarted" ، NullReferenceException ممكن. ضع في اعتبارك تعيين حدث لمتغير محلي قبل استدعاؤه. EventSink.cs 921

public static void InvokeServerStarted()
{
  if ( ServerStarted != null )
    ServerStarted();
}

تستخدم هذه الطريقة استدعاء غير آمن محتمل لمعالج الأحداث RefreshStarted ، كما يشير المحلل.

لماذا هو خطير؟ تخيل هذا الوضع. حدث ServerStarted له مشترك واحد فقط. وفي الوقت الحالي بين التحقق من عدم وجود واستدعاء معالج الأحداث ServerStarted () مباشرة في مؤشر ترابط آخر ، تم إلغاء الاشتراك من هذا الحدث. سيؤدي هذا إلى طرح NullReferenceException .

أسهل طريقة لمنع هذا الموقف هي توفير مكالمة حدث آمنة باستخدام عامل التشغيل "؟.":

public static void InvokeServerStarted()
{
  ServerStarted?.Invoke();
}

تحذير PVS-Studio: V3054 قفل محقق مزدوج غير آمن. استخدم متغير (متغيرات) متغيرة أو بدائية التزامن لتجنب ذلك. Item.cs 1624
private Packet m_RemovePacket;
....
private object _rpl = new object();
public Packet RemovePacket
{
  get
  {
    if (m_RemovePacket == null)
    {
      lock (_rpl)
      {
        if (m_RemovePacket == null)
        {
          m_RemovePacket = new RemoveItem(this);
          m_RemovePacket.SetStatic();
        }
      }
    }

    return m_RemovePacket;
  }
}

يرتبط تحذير المحلل بالاستخدام غير الآمن لنمط القفل المزدوج. كما ترى من الكود أعلاه ، تم تطبيق قفل الاختيار المزدوج لتطبيق النمط التوليدي - Loners. عندما نحاول الحصول على مثيل لفئة Packet عن طريق الوصول إلى الخاصية RemovePacket ، يتحقق getter من حقل m_RemovePacket من أجل المساواة إلى صفر. إذا اجتاز الفحص ، فإننا ندخل إلى نص بيان القفل ، حيث تتم تهيئة حقل m_RemovePacket . ينشأ موقف مثير للاهتمام في الوقت الذي يكون فيه مؤشر الترابط الرئيسي قد قام بالفعل بتهيئة متغير m_RemovePacket من خلال المنشئ ، ولكن لم يتم استدعاء الطريقة بعدSetStatic (). من الناحية النظرية ، يمكن لمؤشر ترابط آخر الوصول إلى خاصية RemovePacket في لحظة غير مريحة للغاية. لن يفشل التحقق من m_RemovePacket من أجل المساواة إلى الصفر ، وسيتلقى مؤشر ترابط الاستدعاء ارتباطًا بكائن جاهز بشكل غير كامل للاستخدام. لحل هذه المشكلة ، يمكنك إنشاء متغير وسيط لفئة Packet في نص عبارة القفل ، وتهيئته من خلال استدعاء مُنشئ وطريقة SetStatic () ، ثم تعيينه إلى متغير m_RemovePacket . في هذه الحالة ، قد يبدو نص بيان القفل كما يلي:

lock (_rpl)
{
  if (m_RemovePacket == null)
  {
    Packet instance = new RemoveItem(this);
    instance.SetStatic();
    m_RemovePacket = instance;
  }
}

يبدو أنه تم إصلاح المشكلة وسيعمل الرمز كما هو متوقع. لكن الأمر ليس كذلك.

هناك نقطة أخرى: لا يقترح المحلل فقط استخدام الكلمة الأساسية المتقلبة . في نسخة إصدار البرنامج ، يمكن للمترجم إجراء التحسين وإعادة ترتيب خطوط الاستدعاء إلى الأسلوب SetStatic () وتعيين متغير المثيل لحقل m_RemovePacket (من وجهة نظر المترجم ، لن تنتهك دلالات البرنامج). ونعود مرة أخرى إلى نفس النقطة التي بدأنا فيها - إمكانية الحصول على المتغير غير المهيأ m_RemovePacket. من المستحيل تحديد متى يمكن أن تحدث إعادة الترتيب هذه ، وما إذا كان سيحدث على الإطلاق: يمكن أن يتأثر بإصدار CLR ، وبنية المعالج المستخدم ، وما إلى ذلك. لا يزال الأمر يستحق الحماية من مثل هذا السيناريو ، وسيكون أحد الحلول (ومع ذلك ، ليس الأكثر إنتاجية) هو استخدام الكلمة الأساسية المتقلبة . لن يخضع المتغير الذي سيتم الإعلان عنه باستخدام المعدل المتغير للتبديل أثناء التحسين بواسطة المترجم. قد يبدو الرمز الثابت أخيرًا كما يلي:

private volatile Packet m_RemovePacket;
....
private object _rpl = new object();
public Packet RemovePacket
{
  get
  {
    if (m_RemovePacket == null)
    {
      lock (_rpl)
      {
        if (m_RemovePacket == null)
        {
          Packet instance = new RemoveItem(this);
          instance.SetStatic();
          m_RemovePacket = instance;
        }
      }
    }

    return m_RemovePacket;
  }
}

في بعض الأحيان قد يكون استخدام حقل متقلب غير مرغوب فيه بسبب النفقات العامة للوصول إلى مثل هذا الحقل. لن نتطرق إلى هذا الموضوع بالتفصيل ، مع الإشارة ببساطة إلى أنه في المثال قيد النظر ، هناك حاجة إلى سجل المجال الذري مرة واحدة فقط (عندما يتم الوصول إلى الخاصية لأول مرة) ، ومع ذلك ، فإن الإعلان عن الحقل على أنه متقلب سيجعل المترجم يقوم بالقراءة والكتابة الذرية التي قد لا تكون الأمثل من حيث الأداء.

لذلك ، هناك طريقة أخرى لتصحيح تحذير المحلل هذا لتجنب الحمل الإضافي من التصريح عن حقل متقلب وهو استخدام النوع <T> البطيء لدعم حقل m_RemovePacketبدلا من قفل الاختيار المزدوج. في هذه الحالة ، يمكن استبدال جسم Getter بطريقة التهيئة ، والتي سيتم تمريرها إلى مُنشئ مثيل Lazy <T> :

private Lazy<Packet> m_RemovePacket = new Lazy<Packet>(() =>
  {
    Packet instance = new RemoveItem(this);
    instance.SetStatic();
    return instance;
  }, LazyThreadSafetyMode.ExecutionAndPublication);

....
public Packet RemovePacket
{
  get
  {
    return m_RemovePacket.Value;
  }
}

سيتم استدعاء طريقة التهيئة مرة واحدة ، عندما يتم الوصول إلى النوع الكسول لأول مرة ، وسيتم ضمان سلامة الخيط في حالة الوصول إلى الخاصية من عدة سلاسل في نفس الوقت من النوع كسول <T> (يتم التحكم في وضع أمان الخيط بواسطة المعلمة الثانية لمنشئ كسول ).

تحذير PVS-Studio: V3131 يتم فحص التعبير "مستهدف" للتوافق مع النوع "IAxe" ، ولكن يتم تحويله إلى نوع "العنصر". HarvestTarget.cs 61

protected override void OnTarget( Mobile from, object targeted )
{
  ....
  else if ( m_System is Lumberjacking &&
            targeted is IAxe && m_Tool is BaseAxe )
  {
    IAxe obj = (IAxe)targeted;
    Item item = (Item)targeted;
    ....
  }
  ....
}

تم التحقق من المتغير المستهدف للتأكد من أنه ينتمي إلى النوع IAxe ، ولكن لم يتم التحقق من الانتماء إلى العنصر ، كما يشير المحلل.

تحذير PVS-Studio: يتم استخدام V3070 Uninitialized "Zero" عند تهيئة المتغير "m_LastMobile". المسلسل ccs 29
public struct Serial : IComparable, IComparable<Serial>
{
  private int m_Serial;

  private static Serial m_LastMobile = Zero;                // <=
  private static Serial m_LastItem = 0x40000000;

  public static Serial LastMobile { .... }
  public static Serial LastItem { .... }

  public static readonly Serial MinusOne = new Serial( -1 );
  public static readonly Serial Zero = new Serial( 0 );     // <=
  ....
  private Serial( int serial )
  {
    m_Serial = serial;
  }
  ....
}

على هذا النحو ، لا يوجد خطأ ، ولكن كتابة هذا ليس جيدًا جدًا. نظرًا لتعيين m_LastMobile لقيمة صفر غير مهيأة ، سيتم إنشاء هيكل باستخدام المُنشئ الافتراضي Serial () ، مما سيؤدي إلى تهيئة m_Serial = 0 . وهذا بدوره يعادل استدعاء المسلسل الجديد (0) . في الواقع ، المطورون محظوظون بأن الرقم التسلسلي يجب أن يساوي 0 ، إذا كان يجب أن يكون هناك أي قيمة أخرى ، فإن هذا قد يؤدي إلى حدوث خطأ.

تحذير PVS-Studio: V3063 دائمًا ما يكون جزء من التعبير الشرطي صحيحًا إذا تم تقييمه: m_Serial <= 0x7FFFFFFF. مسلسل 83

public bool IsItem
{
  get
  {
    return ( m_Serial >= 0x40000000 && m_Serial <= 0x7FFFFFFF );
  }
}

0x7FFFFFFF هي القيمة القصوى التي يمكن أن تحتوي عليها Int32 . لذلك ، مهما كانت قيمة المتغير m_Serial ، ستكون على أي حال أقل من أو تساوي 0x7FFFFFFF .

تحذير PVS-Studio: V3004 عبارة "ثم" تعادل عبارة "آخر". مسلسل 1571

public override void WriteDeltaTime( DateTime value )
{
  ....
  try 
  { 
    d = new TimeSpan( ticks-now ); 
  }
  catch 
  {
    if( ticks < now ) 
      d = TimeSpan.MaxValue; 
    else 
      d = TimeSpan.MaxValue;
  }
  ....
}

يحذر المحلل من جزء من رمز مشبوه تتطابق فيه الفروع الحقيقية والباطلة لعبارة if تمامًا. ربما يجب أن يكون لدى أحد الفروع TimeSpan.MinValue. تم العثور على نفس الرمز في عدة أماكن:

V3004 عبارة "ثم" تعادل عبارة "آخر". Item.cs 2103

public virtual void Serialize( GenericWriter writer )
{
  ....
  
  if( ticks < now ) 
    d = TimeSpan.MaxValue; 
  else 
    d = TimeSpan.MaxValue;
  
  ....
}

V3004 العبارة "ثم" تعادل العبارة "آخر". تسلسل ccs 383

public override void WriteDeltaTime( DateTime value )
{
  ....
  
  if( ticks < now ) 
    d = TimeSpan.MaxValue; 
  else 
    d = TimeSpan.MaxValue;
  
  ....
}

لقد استخدمت "هذا" لسبب ما ، فمن المحتمل جدًا ، من بين أمور أخرى ، أنه لا يمكن الاستغناء عن النسخ واللصق ، تبدو أجزاء التعليمات البرمجية هذه مؤلمة للغاية.

تحذير PVS-Studio: V3051 يلقي نوع الزائدة. الكائن من نوع "العنصر" بالفعل. Mobile.cs 11237

public Item Talisman
{
  get
  {
    return FindItemOnLayer( Layer.Talisman ) as Item;
  }
}
public Item FindItemOnLayer( Layer layer )
{
  ....
}

هذا التحذير محلل يمكن الحصول على إذا كان هناك استخدام مفرط لل كما المشغل . لا يوجد خطأ في هذا القسم من التعليمات البرمجية ، ولكن من غير المنطقي أيضًا إرسال الكائن إلى نوعه الخاص. تحذير

PVS-Studio: V3148 يمكن أن يؤدي صب قيمة "خالية" محتملة من "toSet" إلى نوع قيمة إلى NullReferenceException. خصائص. cs 502

public static string ConstructFromString( .... )
{
  object toSet;
  bool isSerial = IsSerial( type );

  if ( isSerial ) // mutate into int32
    type = m_NumericTypes[4];

  ....
  else if ( value == null )
  {
    toSet = null;
  }
  ....

  if ( isSerial ) // mutate back
    toSet = (Serial)((Int32)toSet);

  constructed = toSet;
  return null;
}

في هذا القسم من التعليمات البرمجية، ونحن مهتمون في الحالات التي يكون فيها متغير القيمة هي لاغية . ثم يكون متغير toSet فارغًا أيضًا . علاوة على ذلك ، إذا كان المتغير isSerial == true ، فسيتم إرسال toSet إلى Int32 ، مما سيؤدي إلى NRE .

يمكنك إصلاح هذا الرمز عن طريق إضافة 0 بشكل افتراضي على سبيل المثال:

toSet = (Serial)((Int32)(toSet ?? 0));

تحذير PVS-Studio: V3031 يمكن تبسيط الفحص الزائد. '||' عامل التشغيل محاط بالتعبيرات المعاكسة 'pack == null' و 'pack! = null'. BODBuyGump.cs 64
public override void OnResponse(Server.Network.NetState sender, RelayInfo info)
{
  ....
  if ( (pack == null) ||
       ((pack != null) &&
        (!pack.CheckHold(
                m_From,
                item,
                true,
                true,
                0,
                item.PileWeight + item.TotalWeight)) ) )
  {
    pv.SayTo(m_From, 503204);
    m_From.SendGump(new BOBGump(m_From, m_Book, m_Page, null));
  }
  ....
}

يمكن تبسيط هذا الكود ، حسب تقارير المحلل:

if ((pack == null) || ((pack != null) && (!pack.CheckHold(....))))

إلى يسار ويمين عامل التشغيل '||' هناك عبارات متناقضة في المعنى. هنا حزمة الاختيار ! = Null زائدة عن الحاجة ، لأنه قبل أن يتم التحقق من حزمة الشرط المعاكس == null ، ويتم فصل هذه التعبيرات بواسطة عامل التشغيل '||'. يمكن اختصار هذا الخط على النحو التالي:

if (pack == null || !pack.CheckHold(....))

تحذير PVS-Studio: V3080 محتمل للإشارة الخالية. خذ بعين الاعتبار فحص "الفائز". CTF.cs 1302

private void Finish_Callback()
{
  ....
  CTFTeamInfo winner = ( teams.Count > 0 ? teams[0] : null );

  .... 

  m_Context.Finish( m_Context.Participants[winner.TeamID] as Participant );
}

دعنا نقول teams.Count هو 0 . ثم الفائز = فارغ. علاوة على ذلك ، يتم الوصول إلى خاصية الفائز. TeamID في التعليمات البرمجية دون التحقق من وجود قيمة خالية ، مما يؤدي إلى الوصول بمرجع فارغ.

تحذير PVS-Studio: V3041 تم التعبير عن التعبير ضمنيًا من النوع "int" إلى النوع "المزدوج". ضع في اعتبارك استخدام قالب صريح لتجنب فقدان جزء كسري. مثال: double A = (double) (X) / Y؛. ستورمز إي سي 87

public static void Gain( Mobile from, Skill skill ) 
{
  ....
  if ( from.Player && 
     ( skills.Total / skills.Cap ) >= Utility.RandomDouble())
  ....
}

يحتوي جزء الشفرة هذا على عملية تقسيم مهارات المتغيرات ، الإجمالي والمهارات ، الكاب ، التي هي من النوع int ، ومن ثم يتم تحويل النتيجة بشكل ضمني إلى النوع مزدوج ، كما يشير المحلل.

تحذير PVS-Studio: V3085 اسم الحقل "typeofObject" في نوع متداخل غامض. يحتوي النوع الخارجي على حقل ثابت باسم مطابق. 744

private static Type typeofObject = typeof( object );
....
private class GroupComparer : IComparer
{
  ....
  private static Type typeofObject = typeof( Object );
  ....
}

تم إنشاء typeofObject المتغير في هذا القسم من التعليمات البرمجية في فئة متداخلة . مشكلتها أنه في الفئة الخارجية يوجد متغير يحمل نفس الاسم ، ويمكن أن تحدث أخطاء بسبب ذلك. من الأفضل عدم السماح بذلك لتقليل احتمالية حدوث مثل هذه الأخطاء بسبب عدم الانتباه.

تحذير PVS-Studio: تستخدم موصّلات الخصائص V3140 حقول دعم مختلفة. WallBanner.cs 77

private bool m_IsRewardItem;

[CommandProperty( AccessLevel.GameMaster )]
public bool IsRewardItem
{
  get{ return m_IsRewardItem; }
  set{ m_IsRewardItem = value; InvalidateProperties(); }
}

private bool m_East;

[CommandProperty( AccessLevel.GameMaster )]
public bool East
{
  get{ return m_East; }
  set{ m_IsRewardItem = value; InvalidateProperties(); }
}

وهنا هناك خطأ ظهر بسبب النسخ واللصق. و مجموعة أسلوب الوصول لل شرق الملكية كان لتعيين قيمة ل m_East ، وليس m_IsRewardItem . تحذيرات

PVS-Studio:

V3012 عامل التشغيل '؟:' ، بغض النظر عن تعبيره الشرطي ، يُرجع دائمًا القيمة نفسها: 0xe7f. TreasureChestLevel2.cs 52

V3012 عامل التشغيل '؟:' ، بغض النظر عن تعبيره الشرطي ، يُرجع دائمًا القيمة نفسها: 0xe77. TreasureChestLevel2.cs 57

private void SetChestAppearance()
{
  bool UseFirstItemId = Utility.RandomBool();

  switch( Utility.RandomList( 0, 1, 2, 3, 4, 5, 6, 7 ) )
  {
    ....
    case 6:// Keg
      this.ItemID = ( UseFirstItemId ? 0xe7f : 0xe7f );
      this.GumpID = 0x3e;
      break;

    case 7:// Barrel
      this.ItemID = ( UseFirstItemId ? 0xe77 : 0xe77 );
      this.GumpID = 0x3e;
      break;
  }
}

نوع من الوهم من خيار :) بغض النظر عن قيمة UseFirstItemId ، this.ItemID سيكون إما 0xe7f في الحالة الأولى، أو 0xe77 في الثانية.

تحذير PVS-Studio: V3066 ترتيب غير صحيح ممكن للوسيطات التي تم تمريرها إلى أسلوب "OnSwing": "المدافع" و "المهاجم". BaseWeapon.cs 1188

public virtual int AbsorbDamageAOS( Mobile attacker,
                                    Mobile defender,
                                    int damage )
{
  ....
  if ( weapon != null )
  {
    defender.FixedParticles(0x3779,
                            1,
                            15,
                            0x158B,
                            0x0,
                            0x3,
                            EffectLayer.Waist);
    weapon.OnSwing( defender, attacker );
  }
  ....
}

public virtual TimeSpan OnSwing( Mobile attacker, Mobile defender )
{
  return OnSwing( attacker, defender, 1.0 );
}

بدا من المشكوك فيه للمحلل أن طريقة OnSwing () تم تمرير الوسيطات في الترتيب العكسي. ربما يكون هذا بسبب خطأ.

تحذير PVS-Studio: تقاطعات المدى V3092 ممكنة ضمن التعبيرات الشرطية. مثال: if (A> 0 && A <5) {...} وإلا إذا (A> 3 && A <9) {...}. HouseFoundation.cs 1883

public static bool IsFixture( int itemID )
{
  ....
  else if( itemID >= 0x319C && itemID < 0x31B0 ) 
    return true;
  // ML doors
  else if( itemID == 0x2D46 ||
           itemID == 0x2D48 ||
           itemID == 0x2FE2 ||
           itemID == 0x2FE4 )
    return true;
  else if( itemID >= 0x2D63 && itemID < 0x2D70 )
    return true;
  else if( itemID >= 0x319C && itemID < 0x31AF ) 
    return true;
  ....
}

نطاقات تم اختبارها تحت ظروف تتقاطع. بدا هذا مريبًا للمحلل. حتى إذا كانت هذه الشفرة تعمل بشكل صحيح ، فلا يزال الأمر يستحق الإصلاح. تخيل موقفًا حيث كنا بحاجة إلى إعادة كتابة جسد الأخير إذا كان الأمر كذلك ، إذا كانت الحالة صحيحة ، فإن الطريقة ترجع كاذبة . إذا كان itemID يساوي ، على سبيل المثال ، 0x319C ، فستظل الطريقة صحيحة . وهذا بدوره سيؤدي إلى ضياع الوقت للبحث عن الأخطاء.

استنتاج


تم إنشاء RunUO منذ فترة طويلة ، وقد تم إنجاز الكثير من العمل ، ومع ذلك ، باستخدام مثال هذا المشروع يمكنك أن ترى فوائد استخدام التحليل الثابت في المشاريع ذات التاريخ. أصدر المحلل لـ 543 ألف سطر من كود المشروع حوالي 500 تحذير (دون احتساب المستوى المنخفض) ، ومعظمها لم يظهر في المقالة بسبب توحيدها. للتعرف على نتيجة التحليل بمزيد من التفصيل ، أقترح استخدام ترخيص مجاني لمشاريع مفتوحة المصدر .



إذا كنت تريد مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فيرجى استخدام رابط الترجمة: Ekaterina Nikiforova. فحص RunUO بواسطة محلل PVS-Studio .

Source: https://habr.com/ru/post/undefined/


All Articles