間違った関数を呼び出すと思われるC++コード(他人によって書かれた)があります。状況は次のとおりです。C++はオブジェクトの完全に間違った(仮想)メソッドを呼び出す
UTF8InputStreamFromBuffer* cstream = foo();
wstring fn = L"foo";
DocumentReader* reader;
if (a_condition_true_for_some_files_false_for_others) {
reader = (DocumentReader*) _new GoodDocumentReader();
} else {
reader = (DocumentReader*) _new BadDocumentReader();
}
// the crash happens inside the following call
// when a BadDocumentReader is used
doc = reader->readDocument(*cstream, fn);
条件がtrueのファイルは正常に処理されます。それが誤ってクラッシュするもの。
class GenericDocumentReader {
virtual Document* readDocument(InputStream &strm, const wchar_t * filename) = 0;
}
class DocumentReader : public GenericDocumentReader {
virtual Document* readDocument(InputStream &strm, const wchar_t * filename) {
// some stuff
}
};
class GoodDocumentReader : public DocumentReader {
Document* readDocument(InputStream & strm, const wchar_t * filename);
}
class BadDocumentReader : public DocumentReader {
virtual Document* readDocument(InputStream &stream, const wchar_t * filename);
virtual Document* readDocument(const LocatedString *source, const wchar_t * filename);
virtual Document* readDocument(const LocatedString *source, const wchar_t * filename, Symbol inputType);
}
次も関連しています:DocumentReaderのクラス階層は次のようになります
のVisual C++デバッガで実行class UTF8InputStreamFromBuffer : public wistringstream {
// foo
};
typedef std::basic_istream<wchar_t> InputStream;
、それはBadDocumentReader上readDocumentコールが
ない呼び出していることを示していますreadDocument(InputStream&, const wchar_t*)
ではなく、むしろ
readDocument(const LocatedString* source, const wchar_t *, Symbol)
これは、すべてのreadDocumentsにcoutステートメントを張り付けることで確認できます。呼び出しの後、source引数はもちろんゴミでいっぱいで、まもなくクラッシュが発生します。 LocatedStringはInputStreamからの1つの引数を持つ暗黙的なコンストラクタを持っていますが、coutをチェックすると呼び出されないことがわかります。何がこれを説明することができる任意のアイデア?
を編集します。その他の可能性のある関連情報:DocumentReaderクラスは、呼び出しコードとは別のライブラリにあります。私はまた、すべてのコードの完全な再構築を行い、問題は残っていました。
編集2:私は、Visual C++ 2008
編集3を使用しています:私は同じ動作で「最低限コンパイルの例」を作ってみましたが、問題を再現することができませんでした。
編集4:
ビリーONealの提案で、IはBadDocumentReaderヘッダ内readDocument方法の順序を変更しようとしました。確かに、私が注文を変更すると、それは関数のどれが呼び出されるかを変更します。これは私の疑惑を確認するために、vtableにインデックスを付けて何か変なことが起こっているように見えますが、何が原因かわかりません。
編集5: ここでは、関数呼び出しの前に数行のための解体だ:
00559728 mov edx,dword ptr [reader]
0055972E mov eax,dword ptr [edx]
00559730 mov ecx,dword ptr [reader]
00559736 mov edx,dword ptr [eax]
00559738 call edx
私は多くのアセンブリを知らないが、それは読者の変数のポインタを逆参照ているように、私には見えます。メモリのこの部分に格納されている最初のものは、vtableへのポインタでなければならないので、それをeaxにデリファレンスします。次に、を最初にのものをedxのvtableに置き、それを呼び出します。メソッドの異なる順序で再コンパイルすることはこれを変更していないようです。常にvtableの最初のものを呼びたいと思っています。 (私はこれを完全に誤解している可能性があり、組み立ての知識は全くありません...)
ありがとうございます。
編集6:問題が見つかりました。誰もが時間を無駄にしていたことを謝ります。問題は、GoodDocumentReaderがDocumentReaderのサブクラスとして宣言されているはずだったが、実際はそうではなかったことです。 Cスタイルのキャストはコンパイラのエラーを抑制していました(@ sellibitzeを聞いたはずですが、あなたのコメントを回答として投稿したい場合は、それを正しいものとしてマークします)。難しいのは、誰かがGoodDocumentReaderに2つ以上の仮想関数を追加して、もはや運が適切な関数を呼び出していないようになるまで、改訂まで純粋な事故によってコードが数ヶ月間働いていたことです。
「DefaultDocumentReader」とは何ですか? –
これを問題を示す最小限のコンパイル可能な例にカットできますか? –
Oli:申し訳ありませんが、 "BadDocumentReader"である必要があります。私は投稿を更新しました。 –