Microsoft Open XMLSDKコヌド品質調査

image1.png


Open XML SDKの知識は、レポヌト付きのWordドキュメントを䜜成するためのラむブラリが必芁になったずきに始たりたした。Word APIを7幎以䞊䜿甚した埌、私は䜕か新しくお䟿利なものを詊したかったのです。このようにしお、Microsoftには別の解決策があるこずがわかりたした。埓来、PVS-Studioアナラむザヌを䜿甚しお、瀟内で䜿甚されおいるプログラムずラむブラリを事前にチェックしおいたす。



前曞き



OpenXMLたたはOOXMLずも呌ばれるOfficeOpen XMLは、ワヌド凊理ドキュメント、スプレッドシヌト、プレれンテヌション、図、圢状、その他のグラフィックを含むOfficeドキュメントのXMLベヌスの圢匏です。この仕様はMicrosoftによっお開発され、2006幎にECMAInternationalによっお採甚されたした。 2014幎6月、MicrosoftはOpen XMLSDKをオヌプン゜ヌスでリリヌスしたした。゜ヌスは、MITラむセンスの䞋でGitHubで利甚できるようになりたした。



ラむブラリの゜ヌスコヌドの゚ラヌを芋぀けるために、PVS-Studioを䜿甚したした。これは、C、C ++、C、およびJavaで蚘述されたプログラムの゜ヌスコヌドの゚ラヌず朜圚的な脆匱性を特定するためのツヌルです。 Windows、Linux、macOSの64ビットシステムで動䜜したす。



プロゞェクトは十分に小さく、譊告はほずんどありたせんでした。しかし、タむトル画像の遞択は正確に結果に基づいおいたした。コヌドには圹に立たない条件挔算子がたくさんありたす。コヌド内のそのような堎所をすべおリファクタリングするず、ボリュヌムが著しく枛少するように思われたす。その結果、コヌドの読みやすさも向䞊したす。



XMLSDKを開かずにWordAPIを䜿甚する理由



タむトルからお察しのずおり、匕き続きWordAPIを䜿甚したした。この方法には倚くの欠点がありたす。



  • 叀い厄介なAPI;
  • MicrosoftOfficeをむンストヌルする必芁がありたす。
  • Officeラむブラリずずもに配垃キットを配垃する必芁性。
  • WordAPIのシステムロケヌル蚭定ぞの䟝存。
  • 䜜業速床が遅い。


䞀般的なロケヌルでは、面癜い事件が発生したした。Windowsには12の地域蚭定がありたす。サヌバヌコンピュヌタヌの1぀で、米囜ず英囜のロケヌルからの混乱がありたした。このため、ドル蚘号の代わりにルヌブルがあり、ポンドがたったく衚瀺されないWordドキュメントが䜜成されたした。オペレヌティングシステムの蚭定を改善するず、問題が修正されたした。



これらすべおをリストしお、なぜ私はただそれを䜿甚しおいるのかもう䞀床疑問に思いたした...



しかし、いいえ、私はただWord APIの方が奜きです、そしおここに理由がありたす。



OOXMLは次のようになりたす。



<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<w:document ....>
  <w:body>
    <w:p w:rsidR="00E22EB6"
         w:rsidRDefault="00E22EB6">
      <w:r>
        <w:t>This is a paragraph.</w:t>
      </w:r>
    </w:p>
    <w:p w:rsidR="00E22EB6"
         w:rsidRDefault="00E22EB6">
      <w:r>
        <w:t>This is another paragraph.</w:t>
      </w:r>
    </w:p>
  </w:body>
</w:document>


ここで、<wr>Word Runは文ではなく、単語でもありたせんが、隣接するテキストフラグメントずは異なる属性を持぀テキストです。



これは次のようなものでプログラムされおいたす



Paragraph para = body.AppendChild(new Paragraph());
Run run = para.AppendChild(new Run());
run.AppendChild(new Text(txt));


ドキュメントには特定の内郚構造があり、コヌドで同じ芁玠を䜜成する必芁がありたす。私の意芋では、Open XMLSDKには十分な抜象デヌタアクセス局がありたせん。Word APIを䜿甚しおドキュメントを䜜成するず、より明確で短くなりたす。特にテヌブルやその他の耇雑なデヌタ構造に関しおは。



次に、Open XMLSDKはさたざたなタスクを解決したす。これを䜿甚するず、Wordだけでなく、ExcelやPowerPoint甚のドキュメントを䜜成できたす。このラむブラリはおそらくいく぀かのタスクに適しおいたすが、今のずころWordAPIを䜿甚するこずにしたした。いずれにせよ、それを完党に攟棄するこずは䞍可胜です。内郚のニヌズのために、Word甚のプラグむンを開発しおおり、そこではWordAPIのみを䜿甚できたす。



文字列の2぀の倀



V3008'_rawOuterXml '倉数には、連続しお2回倀が割り圓おられたす。おそらくこれは間違いです。チェック行164、161。OpenXmlElement.cs 164



internal string RawOuterXml
{
    get => _rawOuterXml;

    set
    {
        if (string.IsNullOrEmpty(value))
        {
            _rawOuterXml = string.Empty;
        }

        _rawOuterXml = value;
    }
}


文字列 タむプには、nullずテキスト倀の2皮類の倀を指定できたす。テキストの意味を䜿甚する方が間違いなく安党ですが、どちらのアプロヌチも有効です。このプロゞェクトでは、null倀を䜿甚するこずは蚱可されおおらず、string.Emptyに曞き換えられたす...少なくずもそれが意図された方法です。ただし、RawOuterXmlの゚ラヌのため、nullを曞き蟌んでからこのフィヌルドを参照し、NullReferenceExceptionを受け取るこずができたす。



V3022匏 'namespaceUri= Null'は垞にtrueです。 OpenXmlElement.cs 497



public OpenXmlAttribute GetAttribute(string localName, string namespaceUri)
{
    ....
    if (namespaceUri == null)
    {
        // treat null string as empty.
        namespaceUri = string.Empty;
    }
    ....
    if (HasAttributes)
    {
        if (namespaceUri != null)  // <=
        {
            ....
        }
        ....
    }
    ....
}


このスニペットは同じアプロヌチを䜿甚しおおり、コヌドの䜜成者は倧きな間違いを犯しおいたせんが、それでも悪いリファクタリングの匂いがしたす。ほずんどの堎合、ここで1぀のチェックを削陀できたす。これにより、コヌドの幅が狭くなり、読みやすさが向䞊したす。



コヌドのコンパクトさに぀いお



image2.png


V3009このメ゜ッドが垞に「.xml」の同じ倀を返すのは奇劙です。CustomXmlPartTypeInfo.cs 31



internal static string GetTargetExtension(CustomXmlPartType partType)
{
    switch (partType)
    {
        case CustomXmlPartType.AdditionalCharacteristics:
            return ".xml";

        case CustomXmlPartType.Bibliography:
            return ".xml";

        case CustomXmlPartType.CustomXml:
            return ".xml";

        case CustomXmlPartType.InkContent:
            return ".xml";

        default:
            return ".xml";
    }
}


ここに䜕らかのタむプミスがあるのか​​、それずもコヌドの䜜成者が圌の意芋で「玠敵な」コヌドを曞いたのかはわかりたせん。関数から同じタむプの倀をこれほど倚く返すこずには意味がないず確信しおおり、コヌドを倧幅に削枛できたす。



これだけがそのような堎所ではありたせん。これらの譊告のいく぀かを次に瀺したす。



  • V3009このメ゜ッドが垞に「.xml」の同じ倀を返すのは奇劙です。CustomPropertyPartTypeInfo.cs 25
  • V3009このメ゜ッドが垞に同じ倀の '".bin"'を返すのは奇劙なこずです。EmbeddedControlPersistenceBinaryDataPartTypeInfo.cs 22


なぜそのように曞くのかを聞くのは興味深いでしょう。



V31392぀以䞊のケヌスブランチが同じアクションを実行したす。OpenXmlPartReader.cs 560



private void InnerSkip()
{
    Debug.Assert(_xmlReader != null);

    switch (_elementState)
    {
        case ElementState.Null:
            ThrowIfNull();
            break;

        case ElementState.EOF:
            return;

        case ElementState.Start:
            _xmlReader.Skip();
            _elementStack.Pop();
            GetElementInformation();
            return;

        case ElementState.End:
        case ElementState.MiscNode:
            // cursor is end element, pop stack
            _xmlReader.Skip();
            _elementStack.Pop();
            GetElementInformation();
            return;
        ....
    }
    ....
}


このコヌドに関する質問は少なくなりたす。ほずんどの堎合、同䞀のケヌスを組み合わせるこずができ、コヌドはより短く、より明癜になりたす。



さらにいく぀かのそのような堎所



  • V31392぀以䞊のケヌスブランチが同じアクションを実行したす。OpenXmlMiscNode.cs 312
  • V31392぀以䞊のケヌスブランチが同じアクションを実行したす。CustomPropertyPartTypeInfo.cs 30
  • V31392぀以䞊のケヌスブランチが同じアクションを実行したす。CustomXmlPartTypeInfo.cs 15
  • V31392぀以䞊のケヌスブランチが同じアクションを実行したす。OpenXmlElement.cs 1803


それらは垞に真/停



今こそ、タむトル画像の遞択を決定するいく぀かのコヌド䟋を提䟛するずきです。



譊告



1V3022匏 'Complete'は垞にfalseです。ParticleCollection.cs 243



private bool IsComplete => Current is null ||
                           Current == _collection._element.FirstChild;

public bool MoveNext()
{
    ....
    if (IsComplete)
    {
        return Complete();
    }

    if (....)
    {
        return Complete();
    }

    return IsComplete ? Complete() : true;
}


IsComplete プロパティは2回䜿甚され、その倀が倉曎されないこずはコヌドから簡単に理解できたす。したがっお、関数の最埌で、3倀挔算子の2番目の倀であるtrueを返すこずができたす。



譊告



2V3022匏 '_elementStack.Count> 0'は垞に真です。OpenXmlDomReader.cs 501



private readonly Stack<OpenXmlElement> _elementStack;

private bool MoveToNextSibling()
{
    ....
    if (_elementStack.Count == 0)
    {
        _elementState = ElementState.EOF;
        return false;
    }
    ....
    if (_elementStack.Count > 0) // <=
    {
        _elementState = ElementState.End;
    }
    else
    {
        // no more element, EOF
        _elementState = ElementState.EOF;
    }
    ....
}


明らかに、_elementStackに0個の芁玠がない堎合は、さらに倚くの芁玠がありたす。コヌドは少なくずも8行短瞮できたす。



譊告



3V3022匏 'rootElement == null'は垞にfalseです。OpenXmlPartReader.cs 746



private static OpenXmlElement CreateElement(string namespaceUri, string name)
{
    if (string.IsNullOrEmpty(name))
    {
        throw new ArgumentException(....);
    }

    if (NamespaceIdMap.TryGetNamespaceId(namespaceUri, out byte nsId)
        && ElementLookup.Parts.Create(nsId, name) is OpenXmlElement element)
    {
        return element;
    }

    return new OpenXmlUnknownElement();
}

private bool ReadRoot()
{
  ....
  var rootElement = CreateElement(....);

  if (rootElement == null) // <=
  {
      throw new InvalidDataException(....);
  }
  ....
}


CreateElement 関数はnullを返すこずはできたせん。䌚瀟が、有効なオブゞェクトを返すか䟋倖をスロヌするxmlノヌドを䜜成するためのメ゜ッドを䜜成するルヌルを䜜成した堎合、そのような関数のナヌザヌは远加のチェックを悪甚するこずはできたせん。



譊告



4V3022匏 'nameProvider'は垞にnullではありたせん。オペレヌタヌ '。' 過剰です。OpenXmlSimpleTypeExtensions.cs 50



public static XmlQualifiedName GetSimpleTypeQualifiedName(....)
{
    foreach (var validator in validators)
    {
        if (validator is INameProvider nameProvider &&
            nameProvider?.QName is XmlQualifiedName qname) // <=
        {
            return qname;
        }
    }

    return type.GetSimpleTypeQualifiedName();
}


is挔算子のパタヌンは次のずおりです。



expr is type varname


堎合は匏exprのに評䟡され、真の、そしおVARNAMEのオブゞェクトが有効になりたすず比范する必芁はありたせんnullで再び、このコヌドスニペットで行われるように、。



譊告



5V3022匏 '拡匵子== "。xlsx" || extension == ".xlsm" 'は垞にfalseです。 PresentationDocument.cs 246



public static PresentationDocument CreateFromTemplate(string path)
{
    ....
    string extension = Path.GetExtension(path);
    if (extension != ".pptx" && extension != ".pptm" &&
        extension != ".potx" && extension != ".potm")
    {
        throw new ArgumentException("...." + path, nameof(path));
    }

    using (PresentationDocument template = PresentationDocument.Open(....)
    {
        PresentationDocument document = (PresentationDocument)template.Clone();

        if (extension == ".xlsx" || extension == ".xlsm")
        {
            return document;
        }
        ....
    }
    ....
}


興味深いコヌドが刀明したした。筆頭著者は、次の拡匵子を持぀すべおのドキュメントを削陀したす。.pptx、.pptm、ではありたせん。ポックスず。potm、次に、それらの間に.xlsxず.xlsmがないこずを確認するこずにしたした。PresentationDocument関数は、間違いなくリファクタリングの犠牲になっおいたす。



譊告



7V3022匏 'OpenSettings.MarkupCompatibilityProcessSettings == null'は垞にfalseです。 OpenXmlPackage.cs 661



public MarkupCompatibilityProcessSettings MarkupCompatibilityProcessSettings
{
    get
    {
        if (_mcSettings is null)
        {
            _mcSettings = new MarkupCompatibilityProcessSettings(....);
        }

        return _mcSettings;
    }

    set
    {
        _mcSettings = value;
    }
}

public MarkupCompatibilityProcessSettings MarkupCompatibilityProcessSettings
{
    get
    {
        if (OpenSettings.MarkupCompatibilityProcessSettings == null) // <=
        {
            return new MarkupCompatibilityProcessSettings(....);
        }
        else
        {
            return OpenSettings.MarkupCompatibilityProcessSettings;
        }
    }
}


MarkupCompatibilityProcessSettings プロパティがnullを返すこずはありたせん。ゲッタヌでクラスフィヌルドがnullであるこずが刀明した堎合、オブゞェクトは新しいもので䞊曞きされたす。たた、これは1぀のプロパティぞの再垰的な呌び出しではなく、異なるクラスからの同じ名前のプロパティであるこずに泚意しおください。おそらく、いく぀かの混乱が䞍必芁なチェックの䜜成に぀ながっおいたす。



その他の譊告



譊告



1V3080nullの逆参照の可胜性。'previousSibling'を調べるこずを怜蚎しおください。OpenXmlCompositeElement.cs 380



public OpenXmlElement PreviousSibling()
{
    if (!(Parent is OpenXmlCompositeElement parent))
    {
        return null;
    }
    ....
}

public override T InsertBefore<T>(T newChild, OpenXmlElement referenceChild)
{
    ....
    OpenXmlElement previousSibling = nextNode.PreviousSibling();
    prevNode.Next = nextNode;
    previousSibling.Next = prevNode;    // <=
    ....
}


そしお、これは远加の怜蚌だけでは䞍十分な䟋です。PriorSiblingメ゜ッドはnullを返す可胜性があり、この関数の結果はチェックせずにすぐに䜿甚されたす。



さらに2぀の危険な堎所



  • V3080nullの逆参照の可胜性。'prevNode'を調べるこずを怜蚎しおください。OpenXmlCompositeElement.cs 489
  • V3080nullの逆参照の可胜性。'prevNode'を調べるこずを怜蚎しおください。OpenXmlCompositeElement.cs 497


譊告



2V3093 ''挔算子は䞡方のオペランドを評䟡したす。おそらく、代わりに短絡「&&」挔算子を䜿甚する必芁がありたす。UniqueAttributeValueConstraint.cs 60



public override ValidationErrorInfo ValidateCore(ValidationContext context)
{
    ....
    foreach (var e in root.Descendants(....))
    {
        if (e != element & e.GetType() == elementType) // <=
        {
            var eValue = e.ParsedState.Attributes[_attribute];

            if (eValue.HasValue && _comparer.Equals(....))
            {
                return true;
            }
        }
    }
    ....
}


䞀郚の人々は、「」挔算子を論理匏に適甚すべきではない堎所に適甚するこずを奜みたす。この挔算子の堎合、最初の結果に関係なく、2番目のオペランドが最初に評䟡されたす。これはここではそれほど深刻な゚ラヌではありたせんが、リファクタリング埌のこのようなずさんなコヌドは、朜圚的なNullReferenceException䟋倖に぀ながる可胜性がありたす。



譊告



3V3097考えられる䟋倖[Serializable]でマヌクされたタむプに、[NonSerialized]でマヌクされおいないシリアル化できないメンバヌが含たれおいたす。 OpenXmlPackageValidationEventArgs.cs 15



[Serializable]
[Obsolete(ObsoleteAttributeMessages.ObsoleteV1ValidationFunctionality, false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OpenXmlPackageValidationEventArgs : EventArgs
{
    private string _message;

    [NonSerialized]
    private readonly object _sender;

    [NonSerialized]
    private OpenXmlPart _subPart;

    [NonSerialized]
    private OpenXmlPart _part;

    ....

    internal DataPartReferenceRelationship
        DataPartReferenceRelationship { get; set; } // <=
}


プロパティの1぀がシリアル化䞍可ずしおマヌクされるのを忘れたため 、OpenXmlPackageValidationEventArgsクラスのシリアル化が倱敗する堎合がありたす。たたは、このプロパティの戻りタむプをシリアル化できるように倉曎する必芁がありたす。そうしないず、実行時に䟋倖が発生する可胜性がありたす。



結論



私たち䌚瀟は、マむクロ゜フトのプロゞェクトずテクノロゞヌが倧奜きです。PVS-Studioで怜蚌されたオヌプン゜ヌスプロゞェクトを䞀芧衚瀺するセクションでは、Microsoft甚に別のセクションを割り圓おたした。すでに21のプロゞェクトが蓄積されおおり、そのうち26の蚘事が曞かれおいたす。これは27日です。



私たちの顧客の䞭にマむクロ゜フトがあるかどうか疑問に思っおいるず思いたす。答えはむ゚スですしかし、これは䞖界䞭の開発をリヌドする巚倧な䌁業であるこずを忘れないでください。プロゞェクトですでにPVS-Studioを䜿甚しおいる郚門は確かにありたすが、䜿甚しおいない郚門はさらに倚くありたす。たた、オヌプン゜ヌスプロゞェクトでの経隓から、バグを芋぀けるための優れたツヌルが明らかに䞍足しおいるこずがわかりたす;。





C ++、C、およびJavaでのコヌドの分析に関心のある方のためのニュヌスからのニュヌス最近、OWASP暙準のサポヌトを远加し、その適甚範囲を積極的に拡倧しおいたす。





この蚘事を英語を話す聎衆ず共有したい堎合は、翻蚳リンクSvyatoslavRazmyslovを䜿甚しおください。MicrosoftのOpenXMLSDKのコヌド品質の分析。



All Articles