16

I want to ban use of iostreams in a code base I have (for various reasons). Is there a way I can inspect symbol files or force the compiler to emit an error when that API is used?

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • Kind of crazy answer for a kind of crazy question: temporarily move the header file for iostream before invoking the compiler. Move it back when the compiler finishes. (This could all be stuffed into the Makefile.) – disatisfieddinosaur Aug 12 '13 at 01:38
  • 8
    Okay, here's a real solution. Use the methods here: http://stackoverflow.com/questions/2726993/g-how-to-specify-preference-of-library-path to add a new path to your g++ linking list. Include an iostream header there but make sure there's a syntax error (or something) in that header. Whenever it's included it'll blow up compilation. – disatisfieddinosaur Aug 12 '13 at 01:40
  • 3
    To improve @skishore solution with headers: use `#error` preprocessor directive to give friendly error message. – milleniumbug Aug 12 '13 at 01:42
  • Or use `static_assert` to cause a compile time error. – Captain Obvlious Aug 12 '13 at 01:49
  • 2
    Only `iostreams` or `istreams` and `ostreams` as well? Just the `cin` and `cout` objects (as well as `cerr` and the wide stream variants) or even instances from the `stringstream` and `fstream` variants? – dyp Aug 12 '13 at 02:09
  • you can find out if cout, cerr,etc were used in the binary after compile time and assuming one of them was actually used - otherwise the compiler would exclude it – Eiyrioü von Kauyf Aug 12 '13 at 02:34
  • Are you stuck with c++? if you are in purely managed .NET code [it is possible](http://stackoverflow.com/questions/15043581/how-to-allow-dll-create-read-update-delete-files-only-in-the-directory-where-the/15044965#15044965) with [partially trusted code](http://msdn.microsoft.com/en-us/library/bb763046.aspx) – Scott Chamberlain Aug 12 '13 at 02:52
  • Have you considered running a dump of the symbols (`nm` in *nix like environments) on the generated binaries? – David Rodríguez - dribeas Aug 12 '13 at 03:38
  • 1
    What if `` includes `` ? C++ has no restrictions on that, unlike C, and skishore's solution would break that. – MSalters Aug 12 '13 at 08:15
  • @skishore: You're assuming I'm using G++. I'm not. – Billy ONeal Aug 12 '13 at 17:50
  • @dyp: The point is that I don't want to pay for the code size -- so I'd want all of it removed. – Billy ONeal Aug 12 '13 at 17:51
  • @Scott: Even on C++/CLI, .NET does not understand iostreams. – Billy ONeal Aug 12 '13 at 17:51
  • @MSalters: Technically it isn't restricted, but I can't see an implementation doing it. Yes, there are a couple of functions on `basic_string` that interact with iostreams (e.g. `std::to_string`), but so long as I don't interact with those I think the linker will remove those unreferenced pieces. – Billy ONeal Aug 12 '13 at 17:52

5 Answers5

36

A simple approach is provide a dummy iostream implementation that does nothing but throw a compile-time error.

The following example assumes a GCC toolchain - I imagine the process is similar with other compilers.

First, create your dummy iostream file:

#error 'Use of iostream is prohibited'

Some dummy application code to demonstrate:

#include <iostream>

int main (int argc, char** argv) {
    std::cout << "foo!";
    return 0;
}

Compile as follows (assuming the dummy iostream and main.cpp are in the working directory):

g++ -I. main.cpp

Compilation fails with the following errors:

In file included from main.cpp:2:0:
./iostream:1:2: error: #error 'Use of iostream is prohibited'
main.cpp: In function 'int main(int, char**)':
main.cpp:4:2: error: 'cout' is not a member of 'std'

Added bonus: symbols usually declared in that file (e.g. cout here) are undefined, and so get flagged in the compiler output as well. As such, you also get pointers to exactly where you're using your prohibited API.

UPDATE: Instructions for Visual C++ 2012.

As @RaymondChen points out in the comments below, a solution tailored to Visual C++ is likely more useful to the OP. As such, the following outlines the process I went through to achieve the same as the above under Visual C++ 2012.

First, create a new console project, using the above C++ code. Also create the dummy iostream header I described above, and place it in a directory somewhere easy to find (I put mine in the main project source directory).

Now, in the Solution Explorer, right click on the project node and select "Properties" from the drop-down list. In the dialog that appears, select "VC++ Directories" from the tree on the left. Prepend the directory containing the dummy iostream file into the list of include directories that appears on the right, separated from the other directories with a semicolon:

Adding new path to include directories list

Here, my project was called TestApp1, and I just prepended its main directory to the $(IncludePath) that was already there. Note that it is important to prepend rather than append - the order of the directories in the list determines the search order, so if $(IncludePath) appears before your custom directory, the system header will be used in preference to your dummy header - not what you want.

Click OK, and rebuild the project.

For me, doing so resulted in the following errors in the VC++ console (edited slightly for brevity):

error C1189: #error :  'Use of iostream is prohibited'
IntelliSense: #error directive: 'Use of iostream is prohibited'
IntelliSense: namespace "std" has no member "cout"

Note that IntelliSense also picks up the (now) illegal use of cout - it is highlighted with an error mark in the editor.

Mac
  • 14,615
  • 9
  • 62
  • 80
  • 6
    -1 for no `std::` before `cout`. And seriously, please don't add a `using`. – Puppy Aug 12 '13 at 02:10
  • 1
    This would generate false positives in code which includes iostream but doesn't use any streams. It also fails to catch code which manually declares std::cout. – Raymond Chen Aug 12 '13 at 02:24
  • 48
    @DeadMG: really? A -1 for something completely irrelevant to the question? You're right, it should be there (and is, now), but I hardly think its worth giving up on the whole answer for. – Mac Aug 12 '13 at 02:25
  • 7
    The compiler is not required to implement standard header file inclusions via files at all - in fact `#include ` doesn't even require that a file called `iostream` exist or even be used. The compiler could, in theory, sprinkle the magic sauce from a database, or the Internet, or via telepathy. – Nik Bougalis Aug 12 '13 at 02:31
  • 1
    @Raymond: if the OP wants to prohibit use of iostreams, then why should including `` be valid and/or useful? I suppose that's a valid argument if there was a legitimate use for something else within the header (i.e. prohibiting use of only part of iostream), in which case this answer is no good - but that's not the scenario the OP is asking about, as far as I can tell. And if someone's manually redefining `std::cout`, then all bets are off anyway. – Mac Aug 12 '13 at 02:34
  • 2
    @NikBougalis: I'm very aware that a compiler is not required to implement standard includes however it wants. That's why, when I gave my example in GCC, I added a disclaimer indicating that it may not work in exactly the same way with other compilers. For what it's worth, this absolutely *does* work with GCC - I did test it before I posted it. – Mac Aug 12 '13 at 02:37
  • You may be using an external source code library that uses iostream only during serialization, and you are careful not to use that feature in your code base, but you still want to use that library. (This is common in template libraries.) – Raymond Chen Aug 12 '13 at 11:01
  • @Raymond: Sure. However, how then would you propose to differentiate between code that is allowed to use iostream (i.e. the external library) and other code? I can't think of an easy solution to that requirement. My example satisfies the requirements as presented by the OP (to the best of my understanding), no more and no less. I certainly never claimed it was a general solution to all such problems. – Mac Aug 12 '13 at 22:43
  • 1
    That's where "inspecting symbol tables" and playing linker games comes in. The OP is most likely using the MSVC toolchain (check his bio) not GCC. – Raymond Chen Aug 13 '13 at 10:10
7

This is a nasty hack, but it should work.

The C standard (and consequently the C++ standard as well) allows preprocessor tokens in #include directives. This is also known as "computed includes".

Thus, adding something like -Diostream to CFLAGS inside your makefile (or to compiler options in your IDE's project settings) should reliably break the build if someone tries to use iostream.

Of course, with an empty macro, the error message will not be very informative, but you could instead use something like -Diostream=DUDE_DONT_USE_IOSTREAM, which will show an error like: DUDE_DONT_USE_IOSTREAM: file not found.

It's also something that you can turn off again without much hassle if you change your mind later. Just remove the build option.

Damon
  • 67,688
  • 20
  • 135
  • 185
  • -1 for compiler-specific hack when the OP doesn't mention his compiler. – Puppy Aug 12 '13 at 14:54
  • 8
    Really, DeadMG? Sure enough your compiler understands `/D` if it doesn't understand `-D`. Are you trying to find a more ridiculous downvote than the one above in the answer by @Mac or what's the issue with you today? :) – Damon Aug 12 '13 at 16:01
  • Even if the compiler doesn't support neither `-D` not `/D` then you can just put `#define iostream DUDE_DONT_USE_IOSTREAM` on top of file if I understand the answer correctly. – Maciej Piechotka Aug 12 '13 at 17:46
  • @MaciejPiechotka: Yes indeed. :) But of course then you would have to do it in every file, using `-D` in the makefile (or in project settings) is much nicer, less intrusive (although it still uses the preprocessor) and more comfortable. – Damon Aug 12 '13 at 19:42
  • @Damon I was more replying to the DeadMG complain that this can be done in 'portable' way. – Maciej Piechotka Aug 12 '13 at 20:48
5

Your idea to inspect symbol files is feasible and very realistic. virtual ~ios_base(); is a single method that all streams will inherit, and which can't easily be inlined since it's virtual and non-trivial. Its presence in an object file is therefore a very strong indication of IOstream use.

MSalters
  • 173,980
  • 10
  • 155
  • 350
0

In addition to compiler-assist method mentioned by Mac you can use generic search functions. For example (I assume zsh shell - for bash doesn't have ** and on Windows you need to find how to do it with Powershell):

 # Find all mentioning on `iostream` `cin` in all files ending in cc in all subdirectories of current directory
 grep iostream **/*.c
 grep cin **/*.cc

If you don't want to/can't use command line you can use your favourite editor and search for unwanted symbols.

I usually combine both methods:

  • Compilation, especially of large project with large number of templates, is slow while searching is fast so you're more productive with search
  • On the other hand search operates is not exact and might miss something. So I'd use header tricks to verify solution done in previous step
  • As final verification you can search for symbols after compilation. It is especially useful if you compile with no optimization. You can use objdump or similar (depending on platform) and watch for imported symbol (this works if you don't say link statically to something using iostreams).
Maciej Piechotka
  • 7,028
  • 6
  • 39
  • 61
  • 5
    @DeadMG: And? C++ is in text files, grep operates on grep files ergo grep can operate on C++. In similar way git's not C++ yet I use it when working on C++ project. – Maciej Piechotka Aug 12 '13 at 17:32
-12

No, not at all. For a very limited subset, you could provide your own definitions, causing the linker to error at the duplicates. This would be very undefined behaviour though. And a good portion is templates that aren't susceptible to this. Without doing drastic things like deleting the iostream header, or using a compiler like Clang and modifying the source code, there's really nothing you can do.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Haven't downvoted you, but AFAIK disabling features built-in to the language is already out of C++ standard's scope, so mentioning that doing X to disable Y built-in into language may be UB is sort of counter-productive. Also, this answer seems to be more like a long comment rather than an actual answer. – milleniumbug Aug 14 '13 at 02:02
  • 7
    You gave a stupid reason to downvote each answer in this question, just so people would upvote your answer. Well, I think that backfired a little bit. –  Sep 05 '13 at 15:41