Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/label statement #467

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

Yi2255
Copy link
Contributor

@Yi2255 Yi2255 commented Nov 14, 2024

It seems that the current version of Fuzzilli lacks code generation for label statement. Its specification ref MDN. This PR aims to support the related features.

The logic implemented is:

  1. Add three operations, i.e. loopLabelBreak, loopLabelContinue,loadLabel, to support label statement.
  2. A label generation statement, i.e. loadLabel operation, randomly inserted just above every loop structures. This approach is taken because I found that using existing context mechanism cannot achieve this specific generation position. For example,
outerLoop: 
for (let i = 0; i < 3; i++) {
    console.log(`Label: outerLoop, Iteration: ${i}`);
}
  1. Due to the nested structure of loops, labels are implemented with a stack-like mechanism. When entering a loop, the generated label is pushed onto the stack, and when the loop ends, the label is popped from the stack. This ensures that break and continue statements can find the appropriate label operation in the current stack.

My concerns are as follows, and I would appreciate any corrections:

  • The implementation of the label stack seems to be somewhat non-standard.
  • This is my first time implementing a complete JSOperation from scratch, and I'm unsure if there are any parts of the code that I may have missed or not properly modified.

Thanks a lot!

@vi3tL0u1s vi3tL0u1s mentioned this pull request Nov 25, 2024
@saelo
Copy link
Collaborator

saelo commented Nov 26, 2024

Thanks! @vi3tL0u1s @Yi2255 Is #476 a superset of this PR as it also adds support in the JS -> FuzzIL compiler? I don't have a strong preference for which PR to review, but if #476 is more feature complete, I'd go for that one?

@Yi2255
Copy link
Contributor Author

Yi2255 commented Nov 26, 2024

We have merged #476 into this PR, and now this PR (#467) can support label statement in both the JsOperation and the JS -> FuzzIL compiler. So I think you could review this PR. Thanks so much!

vi3tL0u1s and others added 2 commits November 27, 2024 13:42
- Implement compilable label statement functionality
- Extend Continue statement to accept label string value
- Add label context tracking in Context analyzer

Performance Analysis:
- Conducted 2-hour comparison test on V8
- Test environment: 16GB RAM, 8 Core CPU VM
- Results:
  * 1% improvement in all coverage metrics (functions, lines, regions, branches)
  * Enhanced coverage in 252 source files
Copy link
Collaborator

@saelo saelo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this, this is really cool!

So some high-level comments. I think the current approach won't work well with our mutation engine. IIUC, we rely on the LoadLabel and LabelContinue and LabelBreak to get the same input variable, and on that being a string. However, we can no longer guarantee that as soon as our mutation engine kicks in (and changes inputs, or splices code, etc.). We would quickly get something like

v0 <- BeginFunctionDefinition
...
EndFunctionDefinition
LoopLabel v0
BeginWhileLoop
    v3 <- CallFunction v1
    LoopBreak v3
EndWhileLoop

Which we can't even properly lift anymore, so we'll generate invalid JS syntax, and that's one thing we try hard to avoid. I also think that something like

foo: <whatever>

while (true) {
    break foo;
}

Is already a SyntaxError, and those are really bad because they can't be avoided with a try-catch and nothing will be executed at all. So we need to avoid that. On the other hand, it would be nice if we could also support something like

foo: if (true) {
    while (true) {
         break foo;
    }
}

I.e. creating labels for things other than loops.

Finally, for fuzzing efficiency, it would be good to avoid "indirect" dependencies where e.g. a BreakLabel "foo" operation only works if we also had a CreateLabel "foo" operation prior to that (the indirect dependency here being the string "foo" that both would need to use). Those are generally tricky because mutating the code will quickly break them.

So overall, I'm wondering if we just want something like this instead:

BeginIf X
    BeginWhileLoop
        ....
        BreakNested 2
    EndWhileLoop
EndIf

So we'd only add two new instructions: BreakNested <depth> and ContinueNested <depth>. We'd then allow <depth> to be too large, in which case we could just use depth % actual_block_depth as the real depth. And then we'd have to autogenerate the labels when necessary during lifting. I think for fuzzing efficiency, this will be optimal because we will always generate syntactically valid code and will only emit labels when necessary. The complexity then lies in the lifter, which needs to figure out where to place labels. For that, it would probably need to build up some helper datastructure that records all valid continue/break targets (BeginIf, BeginXYZLoop, BeginBlock, maybe some more?) and keeps track of the current depth at each point in time, then figures out where each BreakNested and ContinueNested goes, and then generates the labels (e.g. just label1: , label2: etc.) as necessary.

So the lifting will become somewhat complicated. But I think that's ok. In general, in Fuzzilli we prefer "local" complexity (e.g. in the Lifter) over "global" complexity (where multiple different parts of the engine have to work together for something to work).

WDYT? Would this work, and would this also work for the Compiler?

repeatLoop2: do {
console.log("Running once");
break repeatLoop2;
} while (false);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should this be while (true), otherwise we'll anyway only run once?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi saelo, according to your feedback, I reimplemented the feature of the label statement under the context of loop structure. The new PR #479 outlines the design approach.
If there are any issues, please point out so we can address them. Thanks a lot!

console.log("Looping...");
break outerLoop3;
console.log("Cannot be printed...");
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also add some tests with nested loops where we break out of two or more loops?

}
console.log(`i = ${i}, j = ${j}`);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also something like

ifLabel: if (true) {
    loopLabel: while (true) {
        break ifLabel;
    }
    console.log("Never reached");
}

But it'll of course depend on whether we can support this type of code.

@@ -0,0 +1,27 @@
labelBlock: for (let i = 0; i < 1; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding these tests, that's great! I think it would also be good to add some LifterTests.

@Yi2255 Yi2255 mentioned this pull request Dec 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants