Minecraftのモード、カタルーニャ語、およびトリナリーオペレーターとの非明確な相互作用—私たちのアナリストがどれだけ多くのものに遭遇したか! だから、あなたとハイライトを共有する時間です。 導入 今年は、多くのオープンソースプロジェクトをチェックし、 彼らについて 記事 今、私たちは、最も興味深いバグと奇妙なコードの断片の10を選択しました。選択とランキングは、もちろん、主観的で、私たちの独自の冷静さの感覚に基づいています。 いよいよ時間だ!行こう! 第10話 右、左、右 We open our top with a case from トップページへ . In a way, the project resonates with us: just like PVS-Studio, it analyzes languages—only natural ones, not artificial. 言語ツール 面白いのは、このエラーが私たちのアナリストに関連しているということです。しかし、私たちはそれを逃すことができないほど面白いと感じます。 Look at the code fragment: public String getEnclitic(AnalyzedToken token) { .... if (word.endsWith("ه")) { suffix = "ه"; .... else if ((word.equals("عني") || word.equals("مني")) && word.endsWith("ني") // <= ) { suffix = "ني"; } .... } PVSスタジオの警告: 「word.endsWith(「ニー」)」という表現は常に間違っています。 V6007 アラビア語タグ:Java 428 もし私たちが、英語やフランス語のような左向き(LTR)言語のネイティブスピーカーが通常行うように、ストレージ常数を調べてみると、アナリストの警告は公平に見えます。 -アラビア語を話す人々は文字通りの終わりを期待し、彼らは実際にその始まりを見るでしょう。 アラビア語を話す non can't ちなみに、RTLの書き込みがあることを知っていると、その条件が実際に常に存在することに気づきます。 偽物ではない。 true それは私たちに好奇心をもたらしました:Javaはどのようにして右から左の言語を扱うのですか? すべてはパイのように簡単です:Javaはテキストの視覚化の代わりにUnicodeを扱っています。 つまり、母国語の人が読んで書く順序です。 論理的な順序で保存されています。 これは、Java がRTL テキストを処理するために特別な変換を実行する必要がないことを意味します. The string remains a regular sequence of Unicode code points, and operations such as obtaining its length, accessing characters, or extracting a substring work the same for all languages. この文字列は、Unicode コードのポイントの通常の順序であり、その長さを取得する、文字にアクセスする、またはサブストリングを抽出するなどの操作は、すべての言語で同じです。 したがって、このようなチェックのために: System.out.println("مرحباً بالجميع".endsWith("بالجميع")); 結果は、 左から右まで読むと、文字列は「بالجميع」ではなく「بالجميع」で始まります。 true それでも、アナリストがここで技術的に間違っているので、私たちはランキングで10位に置いています。 詳しく知りたい方は、この記事で LanguageTool をどのように分析したかについての全文を読み取ることができます。 . 記事 第9位 カタルーニャ語 再びランキングに登場! 言語ツール カタルーニャ語モジュールは、 例えば、「adéu」を「adieu」に、「dóna」を「dona」に、「vénen」を「venen」に、その他に変換する。 removeOldDiacritics() private String removeOldDiacritics(String s) { return s .replace("contrapèl", "contrapel") .replace("Contrapèl", "Contrapel") .replace("vés", "ves") .replace("féu", "feu") .replace("desféu", "desfeu") .replace("adéu", "adeu") .replace("dóna", "dona") .replace("dónes", "dones") .replace("sóc", "soc") .replace("vénen", "venen") .replace("véns", "véns") // <= .replace("fóra", "fora") .replace("Vés", "Ves") .replace("Féu", "Feu") .replace("Desféu", "Desfeu") .replace("Adéu", "Adeu") .replace("Dóna", "Dona") .replace("Dónes", "Dones") .replace("Sóc", "Soc") .replace("Vénen", "Venen") .replace("Véns", "Vens") .replace("Fóra", "Fora"); } 一見すると、それは大丈夫に見えます:それは機能し、修正し、誰にも迷惑をかけることはありません。 しかし、アナリストにはもう一つの点がある。 Function 'replace' receives an odd argument. The '" véns " argument was passed several times. V6009 カタルーニャ.java 453 方法が「véns」を「véns」に置き換えるという問題は、言い換えれば、まったく何も置き換えられていない可能性が高いが、開発者がこのブロックで作業したとき、彼らは単に元の単語をコピーし、文字を置き換えたが、この場合、第二の論点で文字を変更するのを忘れた。 .... .replace("véns", "vens") .... 私たちの人工言語アナリストは、自然言語アナリストでエラーを検出することができたので、この警告は私たちのトップリストに位置しています。 第8話 最後の線効果 更に進んでいこう、今、私たちはエラーについて話しています。 . エラスティック コードの部分をご覧ください: @Override public boolean equals(Object obj) { .... KeyedFilter other = (KeyedFilter) obj; return Objects.equals(keys, other.keys) && Objects.equals(timestamp, other.timestamp) && Objects.equals(tiebreaker, other.tiebreaker) && Objects.equals(child(), other.child()) && isMissingEventFilter == isMissingEventFilter; // <= } The PVS-Studio warning: V6001 There are identical sub-expressions 'isMissingEventFilter' to the left and to the right of the '==' operator. PVS-Studio warning: V6001 There are identical sub-expressions 'isMissingEventFilter' to the left and to the right of the '==' operator. PVS-Studio warning: V6001 There are identical sub-expressions 'isMissingEventFilter' to the left and right of the '==' operator. タイトル:Java 116 前例と同様に、我々は文字列を扱っているが、わずかな微妙さ:エラーは同一のコードの最後のブロックラインで発生する。 この状況は、開発中に非常に頻繁に発生し、開発者がそれに名前を与えたことがよくあります。 このプロジェクト内でも、これは単一のケースではありません:同じコード内の他のブロックの最後の行に類似のエラーが表示されます。 the last line effect @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; IndexError that = (IndexError) o; return indexName.equals(that.indexName) && Arrays.equals(shardIds, that.shardIds) && errorType == that.errorType && message.equals(that.message) && stallTimeSeconds == stallTimeSeconds; // <= } The PVS-Studio warning: V6001 There are identical sub-expressions 'stallTimeSeconds' to the left and to the right of the '==' operator. インデックスエラー.java 147 このような効果についてもっと知りたい場合は、以下の記事を読むことをお勧めします。 私たちの最初の出会い: The Last Line Effect 現象についてのより詳細な議論: The last line effect explained. As this error demonstrates such a curious phenomenon, we award it a place in our top list. 第7話 失われた場所 では、第7位に進みましょう。 我々はすでに3つのJavaエラーを調べたが、まだMinecraftに遭遇していない。 コード・フラグから見る : トップ > NPC+ private String getTimePast() { .... if(selected.timePast > 3600000){ int hours = (int) (selected.timePast / 3600000); if(hours == 1) return hours + " " + StatCollector.translateToLocal("mailbox.hour"); else return hours + " " + StatCollector.translateToLocal("mailbox.hours"); } int minutes = (int) (selected.timePast / 60000); if(minutes == 1) return minutes + " " + StatCollector.translateToLocal("mailbox.minutes"); else return minutes + " " + StatCollector.translateToLocal("mailbox.minutes"); } PVSスタジオの警告: 「あの時」という言葉は「else」という言葉に等しい。 ワイ6004 GuiMailbox.java 76 ここでは、ローカライゼーションエラーにつながる文字列が表示されることがあります。分数に応じて、「分」または「分」が表示されなければなりません。 and 枝は同一であり、常に「分」を出力する。 then else この著者としての 」と書かれているので、テキストのリソースは コードを修正するには、開発者は1つの文字を削除する必要があります。その後、正しい単語フォームはモードがサポートするすべての言語に表示されます。 記事 mailbox.minute もちろん、このエラーはそれほど深刻ではありませんが、問題なく再現できるので、私たちのトップで7番目にランクインしています。 ♪ 第6話「あれが最後の」 次に、エラーから、 : 自動車MQ public static final int MAX_TYPE_NUMBER = 20; private static final LongAdder[] USAGE_STATS = new LongAdder[MAX_TYPE_NUMBER]; .... public static ByteBuf byteBuffer(int initCapacity, int type) { try { if (MEMORY_USAGE_DETECT) { .... if (type > MAX_TYPE_NUMBER) { counter = UNKNOWN_USAGE_STATS; } else { counter = USAGE_STATS[type]; // <= .... } .... } .... } } PVSスタジオの警告: インデックスの「タイプ」が限界を超えている可能性があります。 V6025 トップページ > ジャガイア 151 これはかなり一般的なエラーです(正直、このトップリストの著者自身がしばしばそれを作成しているほど一般的です)。 . Due to a minor error in the condition, an 解析器が示すコードラインで発生する可能性があります。 MAX_TYPE_NUMBER OutOfBoundsException 起こりうるのは、なぜなら それは、その場で、その場で、 ブランド、The parameter will have , which exceeds the maximum valid array index by one. In this case, an out-of-bounds exception will be thrown, since the very first line accesses the array using the インデックス if (type > MAX_TYPE_NUMBER) else type MAX_TYPE_NUMBER type To fix it, we just need to use 代わりに . >= > このエラーが著者の心に近いので、トップリストで6位にランクインします。 第5位:HTMLの外部データ Now it is 's turn. Look at the code: ジェティ public class HelloSessionServlet extends HttpServlet { .... @Override protected void doGet( HttpServletRequest request, HttpServletResponse response ) { .... String greeting = request.getParameter("greeting"); // <= if (greeting != null) { .... message = "New greeting '" + greeting + "' set in session."; // <= .... } .... PrintWriter out = response.getWriter(); out.println("<h1>" + greeting + " from HelloSessionServlet</h1>"); // <= out.println("<p>" + message + "</p>"); // <= .... } } PVSスタジオの警告: . Possible XSS injection. Potentially tainted data in the 'message' variable might be used to execute a malicious script. ワールド530 トップページ > java 70 アナリストは、ある可能性があると指摘している。 . XSS注射 コードでは、リクエストから文字列を取得し、それを使用してメッセージを生成し、それらのメッセージを使用して HTML ページを生成します。 in any way, the XSS injection is indeed possible. sanitized もし、外部を通過した場合 は ・JavaScript ページを開くと、他のJSコードと同じように実行されます。 message <script>alert("XSS Injection")</script> alert("XSS Injection") もしこれが実際の脆弱性であれば、なぜリストに上位にランクされないのでしょうか? なぜなら、デモの例の一部だからです. 言い換えれば、Jettyで起こりうることを確認するためにコードを表示します. しかし、デモの例を生産にコピーすることはできなかったことを覚えておく価値があります. 結局のところ、不安全なコードを表示することは最善の実践ではありません。 初代として 実際のプロジェクトにおける分析警告は、デモの例ですが、上位5位に置かれています。 タイン 第4位 ダブルのみ エラーを見る→ . エラスティック public static Number truncate(Number n, Number precision) { .... Double result = (((n.doubleValue() < 0) ? Math.ceil(g) : Math.floor(g)) / tenAtScale); return n instanceof Float ? result.floatValue() : result; // <= } PVSスタジオの警告: この表現の結果は implicitly cast to 'double'. Check if program logic handles it correctly. プログラムの論理がそれを正しく処理しているかどうかを確認します。 ワイ6088 イギリス 122 この方法は、指定された精度に従って渡された数を切断します. 整数値では、数字の数を減らしますが、割合値では、割合数に適用された論理を上記のスニップで示します。 Judging by the ternary operator, in the case of fractions, developers want the method to return an identical number with which it came to the method (either or ). But a subtle but important detail has been overlooked. Float Double Ternary operator is an expression, which means its result must have a single and predetermined type. この時点で、その結果は単一で事前定義されたタイプでなければなりません。 come into play. In short, the following applies: 数字の文脈 異なるタイプのオブジェクトが表現に表示される場合は、単一の共通型に変換されます。 この場合、一般的なタイプは Double であり、Double と Float の両方を表します。 つまり、期待にもかかわらず、方法は常に返します。 割合数を処理する場合 Double コンパクトなレコーディングフォーマットは本当に私たちを落とすことができます. A fix may look like this: public static Number truncate(Number n, Number precision) { .... if (n instanceof Float) { return result.floatValue(); } else { return result; } } この非明確な言語の色合いのために、エラーはトップ10の4番目の場所を獲得します。 第3話 あなたの番を待ってください! 今度は、金メダルを手に入れ、金メダルを手に入れた。 コードの部分をご覧ください: インテリジェントアイデア public final class UsageType { .... public static final UsageType DELEGATE_TO_ANOTHER_INSTANCE_PARAMETERS_CHANGED = new UsageType( UsageViewBundle.messagePointer( "usage.type.delegate.to.another.instance.method.parameters.changed" ) ); private static final Logger LOG = Logger.getInstance(UsageType.class); public UsageType(@NotNull Supplier<....> nameComputable) { myNameComputable = nameComputable; if (ApplicationManager.getApplication().isUnitTestMode()) { String usageTypeString = myNameComputable.get(); if (usageTypeString.indexOf('{') != -1) { LOG.error(....); } } } .... } PVSスタジオの警告: 「DELEGATE_TO_ANOTHER_INSTANCE_PARAMETERS_CHANGED」の初期化が「LOG」の初期化の前に表示されます。 ワイ6050 UsageType.java 46 アナライザは、静的フィールド初期化の際に周期的な依存性について警告します。 The constructor is used to initialize the フィールド: and we access logger in this constructor. DELEGATE_TO_ANOTHER_INSTANCE_PARAMETERS_CHANGED LOG フィールド . As we can see, 上に位置する つまり、初期化されたもので、その結果、コンクリエーターが実行されると、 それにアクセスしようとすると、NPEに遭遇します。 彼らが宣言された順序で初期化されている。 DELEGATE_TO_ANOTHER_INSTANCE_PARAMETERS_CHANGED LOG LOG きっと null 上記のほとんどのケースと同様に、修正は非常に単純になります。 上の声明 . LOG DELEGATE_TO_ANOTHER_INSTANCE_PARAMETERS_CHANGED 第2話 ブロックはどこへ? 私たちは銀メダリストに移りました 再び Minecraft mod に会い、 . トップ > NPC+ コードの部分をご覧ください: public int range = 5; public int speed = 5; .... public boolean aiShouldExecute() { healTicks++; if (healTicks < speed * 10) return false; for (Object plObj : npc.worldObj.getEntitiesWithinAABB( EntityLivingBase.class, npc.boundingBox.expand( range, range / 2, // <= range)) ) { .... } healTicks = 0; return !toHeal.isEmpty(); } PVSスタジオの警告: 表現は implicitly casted from 'int' type to 'double' type. Consider using an explicit type cast to avoid the loss of a fractional part. Example: double A = (double)(X) / Y;. 表現は implicitly casted from 'int' type to 'double' type. Consider using an explicit type cast to avoid the loss of a fractional part. 例: double A = (double)(X) / Y;. ワイ6094 JobHealer.java 41 このコードスニップでは、ゲームは定期的に(コードに基づいて) field) scans for entities inside a rectangular area with a radius defined by NPCの医師を中心に speed range Since the Method has the 署名で、アナリストはこの部分について警告するのが絶対に正しい: どこの方法に移行したか 「じゃあ何?」あなたは尋ねるかもしれません。私たちはこれは本当に奇妙だと思うので、私たちはこのケースを調査することを決めました。 expand expand(double x, double y, double z) int double この小さな欠点は、次のような結果につながります: ラジウムが奇妙な数である場合、Y軸を評価するとき、我々は上部と底部の半分のブロック(すなわち、全体のブロック)を失います。 NPCドクターの衝突は白で強調されています。 現在実施されている衝突(5 / 2)は赤で強調されています。 整数分割なしの衝突(5 / 2.0)は緑で強調されます。 ご覧のとおり、使用すると、 literal of the 次に、分割はもはや整数ではなく、結果として、衝突は私たちが望む正確な半径をカバーします。 2.0 double そして、それが正しく働いたことを確認するために、著者 「宇宙に尋ねなきゃいけない」と。 記事 特に、開発者は、Minecraft 1.12.2 のオリジナル モードでこのエラーを修正しました。残念ながら、私は直接の証拠を提供できないので、宇宙に尋ねたとしましょう。 特に、開発者は、Minecraft 1.12.2 のオリジナル モードでこのエラーを修正しました。残念ながら、私は直接の証拠を提供できないので、宇宙に尋ねたとしましょう。 .... this.npc.world.getEntitiesWithinAABB( EntityLivingBase.class, npc.getEntityBoundingBox().expand(range, range / 2.0, range) ); .... 開発者がオリジナルの修正でバグを修正したので、ここでも同じことが適用されるべきです。 Since devs have fixed the bug in the original modification, the same should apply here. 第1位 失われたボーナスの謎のケース ちなみに、金メダルは・・・ ! Minecraftのバグの多くは、チームの半分がモードやプラグインの開発に無数の時間を費やしたからなのかもしれません。 トップ > NPC+ では、コードに進みましょう: .... int startIndex = -1; boolean number = false; try { startIndex = Integer.parseInt(bonusID); number = true; } catch (Exception var34) { number = false; } for (startIndex = 0; startIndex < bonuses.length; ++startIndex) { .... if (number && startIndex == startIndex || // <= !number && bonusValues[startIndex][0].equals(bonusID) ) { noNBTText = bonusValues[startIndex][0] + ";" + bonusValueString; bonuses[startIndex] = ""; bonuses[startIndex] = noNBTText; .... break; } } .... PVSスタジオの警告: V6001 「==」オペレーターの左と右に同一のサブ表現「startIndex」があります。 スクリプトDBCPlayer.java 289 V6007 「!number」は常に正しい。 スクリプトDBCPlayer.java 289 V6033 同じキー「startIndex」を持つ項目がすでに変更されています。 スクリプトDBCPlayer.java 293 Do warnings really indicate errors? And if so, what does it lead to? Here, a whole detective investigation with an interesting ending awaits us. まず、最初の警告を見てみましょう 比較 この記事の不思議な著者は、探偵の帽子と長いコートを着て、クラスでヒントを探しに行く。 startIndex ファイル: : ScriptDBCPlayer.java(224) .... int num = -1; boolean number; try { num = Integer.parseInt(bonusID); number = true; } catch (Exception var33) { number = false; } for (int i = 0; i < bonuses.length; ++i) { .... if (number && i == num || !number && bonusValues[i][0].equals(bonusID) ) { bonuses[i] = ""; break; } } .... The code looks very similar, but it's no coincidence that in our team, we call the author of the article "The Keen Eye"—he found the difference! In the second case, the variables are IN THE block and in the ループ: They're そして 最初の部分では、最初に初期化します。 IN THE block, and then iterate through it in a ループ different try for num i startIndex try for Therefore, in the second fragment, two different variables are compared in an identical check: そして 最初の1つには、アナリストが指摘したものがある―― . num i startIndex == startIndex So now we know what the first fragment was supposed to look like. ? But what does the error impact この時点で、探偵はクラスそのものの名において重要なヒントに気付いた: プレフィックスは、クラスがゲームのスクリプトメカニズムで使用されていることを意味します。エラー付きの方法は、ゲームの属性「強さ」、「敏捷性」などにボーナスを割り当てることに責任があります。論理的に言えば、この方法は、ゲーム内のスクリプトから特定のボーナスインデックスを読み、その後、相応の修正を適用します。 Script まず、ボーナスインデックスを解析する必要があります。 ブロック: try try { num = Integer.parseInt(bonusID); number = true; } .... そしてその後、ループで、ボーナスに渡された値を設定します: for (int i = 0; i < bonuses.length; ++i) { .... if (number && i == num || ....) { noNBTText = bonusValues[i][0] + ";" + bonusValueString; bonuses[i] = ""; bonuses[i] = noNBTText; .... break; } } しかし、エラーを含むフラグメントでは、スクリプトから得られた属性インデックスがゼロで書かれ、それからそれ自体と比較されます。 try { startIndex = Integer.parseInt(bonusID); number = true; } catch (Exception var34) { number = false; } for (startIndex = 0; startIndex < bonuses.length; ++startIndex) { .... if (number && startIndex == startIndex || // <= !number && bonusValues[startIndex][0].equals(bonusID) ) { noNBTText = bonusValues[startIndex][0] + ";" + bonusValueString; .... } } つまり、スクリプトで渡されたボーナス値は常に、 ボーナス なぜなら is overwritten in the 状態がゼロになり、それからそれ自体と比較されます。今、問題はクリスタルクリアです。 first startIndex for まず、「Strength」のためのボーナスを作成するゲーム内脚本を書く必要があります。 First, let's create two bonuses with values of 1, and then set their values to 5 and 15, respectively: ♪ This is what in-game scripts look like. We created bonuses for "Strength" with indices 0 and 1, and then assigned them the values. 現在、理論的には、「強さ」からこれらの2つのボーナスを表示すれば、5と15を得るだろうか? コードにエラーがなかったら、それが起こるだろうが、上記のブロックから理解したように、エラーは最初のボーナスの値が書き換えられるようになります。 To confirm this behavior, let's write another in-game script that simply prints the bonus values: ♪ その結果、我々は得る: ♪ Indeed, the second bonus in "Power" also has an initial value of 1, while the first bonus now equals 15. It turns out that we first set the first bonus to 5, and then overwrite it with a value of 15. Our hypothesis above turns out to be true. The case has been solved. 探偵の仕事の量と、このバグが実践で信頼的に再現できるという事実を考慮して、私たちは自信を持ってそれを授与します。 in our top ten! honorable first place Wrapアップ Yay! 我々は、最も奇妙な、熱い、教育的なバグのすべての10を乗り越え、我々はあなたが私たちと同じくらいの旅を楽しんだことを願っています! あなたがこのトップについて自分の考えを持っているなら、あるいはお気に入りのバグを持っているかもしれません、我々はあなたがコメントでそれらを共有することを楽しみにします。 他のプロジェクトのチェックについてもっと読みたい場合は、私たちの または ON . ブログ このページ If you feel inspired to check your individual projects and want to try PVS-Studio analyzer yourself, follow the そして、あなたが学生、教師、またはC、C++、C#、またはJavaでオープンソースのプロジェクトのメンテナンス者であれば、あなたは無料でPVS-Studioを使用することができます。 . リンク ここ 残りはあいさつだけ!おめでとうございます!おめでとうございます!