5

There is a complex piece of code that does many complex mathematical operations.

When it is built and tested by maven on jdk 1.7 it passes all the tests. When using jdk 1.8 it fails.

Trying to find the spot where the calculations go wrong in a debugger seems almost hopeless.

What are my options? Is there a tool that can scan for incompatibilities between jdk 1.7 and 1.8 in my code?

Is my best option to run the code in two separate debuggers and see where the difference would be?

EDIT:

@Philipp Claßen That is the most likely cause so far. I was hoping there would be an automated way of checking for this.

@dkatzel The code was not written by me, poorly commented and does scientific calculations that are "woodo" to me.

@Mike Samuel I see no benefit of this approach over running two debuggers in parallel.

Thank you all for helping. Seems that two debuggers is the best way to go.

EDIT 2 The author of the original code was relying on hash map ordering. That was the problem.

Anton
  • 2,282
  • 26
  • 43
  • Check the release notes for clues. Otherwise, I think you're going to have to run two debuggers. – Jonathan M Dec 12 '14 at 19:58
  • 1
    can you please post some code? – Enrique San Martín Dec 12 '14 at 19:59
  • I can guarantee that seeing code will not help. Unless you would like to go through thousands of lines. – Anton Dec 12 '14 at 20:01
  • He means something that we could reproduce the issue with on jdk 1.7 and jdk 1.8 – user1071777 Dec 12 '14 at 20:07
  • I see. That is kind of him, but I'm not asking to find the issue, I'm asking about the methods to find the issue. – Anton Dec 12 '14 at 20:10
  • you mentioned having test suite.. and that calculations going wrong.. are you sure its the calculations going wrong or is it just some other errors that are failing the test cases. can you post some stack traces of the test cases that fail? may be that will give some clue. – Shaunak Dec 12 '14 at 20:18
  • The test is an assertion that a calculated value is the same as the expected value, so there is no stack trace. – Anton Dec 12 '14 at 20:40
  • 1
    You describe two scenarios where it should be five: 1) build using `1.7` and tested/executed on `1.7`, 2) build using `1.7` and tested/executed on `1.8`, 3) build using `1.8` with `-target 1.7` and tested/executed on `1.7`, 4) build using `1.8` with `-target 1.7` and tested/executed on `1.8`, 5) build using `1.8` with `-target 1.8` and tested/executed on `1.8`. It would be useful to know which of these scenarios fails or whether/which of the failing scenarios produce same results. – Holger Dec 12 '14 at 20:44
  • Good point. There should be 5. – Anton Dec 12 '14 at 20:48
  • 1
    If you suspect that there might be code relying on `HashMap` ordering, you may run a test on `jdk1.7` where you replaced the `HashMap` implementation enforcing a different order. If that’s the only difference to the original `jdk1.7` you will know soon if that’s the issue. – Holger Dec 12 '14 at 20:52

4 Answers4

7

Look out for sources of nondeterminism.

Especially code that relies on the ordering of elements in Collections like HashMaps is dangerous, as the ordering is unspecified and depends on the JVM.

I have seen it in every JVM upgrade that such (buggy) code worked by luck and broke immediately once the JVM implementation changed.

Philipp Claßen
  • 41,306
  • 31
  • 146
  • 239
3

Add logging statements to log every intermediate result. You can use something like

static void dumpDoubleWithLineNumber(double d) {
  StackTraceElement[] stack = Thread.currentThread().getStackTrace();
  // Thread.getStackTrace() and this method are elements 0 and 1
  StackTraceElement caller = stack[2];
  System.err.println(
      caller.toString()
      + " : " + d 
      + " / " + Long.toString(Double.toLongBits(d), 16));
}

Then run it once under Java 7, once under Java 8, and diff the results.

Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
  • I am sorry , but considering that he already has test cases, and the code is 1000s of lines, isn't this a little Hello World approach? I would rather start with Test cases and their stack traces.. – Shaunak Dec 12 '14 at 20:21
  • @Shaunak, I don't know what a Hello World approach is, but the test stack traces only tell you where the failure was detected. A diff of logs from one failing test will tell you where the divergence started. 1000's of lines is quite a small program. Putting in a few hundred log statements should take 20 minutes tops. Trying to reason backwards from a test failure might take hours or days. – Mike Samuel Dec 12 '14 at 20:24
  • Exactly what we write test cases for! and looks like he already has good set of test cases that caught the problem. – Shaunak Dec 12 '14 at 20:26
  • I actually liked this solution. – Archimedes Trajano Dec 12 '14 at 20:28
  • 1
    @Shaunak, The tests have told Andrey that there is a problem, but Andrey hasn't caught it yet. Telemetry can help with that. – Mike Samuel Dec 12 '14 at 20:28
3

Seems like you have a good set of tests that caught the problem. Perhaps a good start would be to compare the tests that pass and the tests that fail. What did the tests that fail do differently?

If the tests that fail do too much, then you need more finer grained tests. Tests should really only test one thing in isolation. This means you should have lots of tests that test each part of the complex calculation.

New versions of Java really try hard not to break old working code so it is unlikely a bug in the JDK...however it is possible...

dkatzel
  • 31,188
  • 3
  • 63
  • 67
0

If there is a specific object, you want to monitor and the points, where the object is changed are not too many, you could try to set conditional breakpoints, which will print out information to System.out instead of supending the Thread. That works fine for eclipse, may be also for other IDEs
For example, to monitor the value d at a specific point of code, you create a breakpoint with the following condition:

System.out.println(Thread.currentThread().getStackTrace()[1] + " value="+d); return false;

This condition is never true but always evaluated, so it prints out something like that:

test.Main.main(Main.java:23) value=1.0

If this works in your IDE, you can run your program with Java 7 and java 8 in the IDE and comparing the output. If you can identify the line where the differences occurs, you may go on with conventional debugging.

May be you can find more here : http://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-manage_conditional_breakpoint.htm

Gren
  • 1,850
  • 1
  • 11
  • 16