Try it Live Run Bytecode examples in the interactive playground
bytecode_get_next_pc(bytecode_t bytecode, size_t current_pc, size_t *next_pc) boolCalculate the next program counter position after the instruction at current_pc. Parameters:
bytecode: Bytecode to analyze
current_pc: Current program counter position
next_pc: Output parameter for next PC
Returns: bool - true if next_pc is valid, false if at endExample: #include "primitives.h"
bytecode_t code = bytecode_from_hex ( "0x60016002015b00" );
size_t next_pc;
if ( bytecode_get_next_pc (code, 0 , & next_pc )) {
printf ( "Next PC: %zu \n " , next_pc); // 2
} else {
printf ( "At end of bytecode \n " );
}
bytecode_free (code);
Defined in: primitives.h
How It Works
getNextPc() handles the variable-width nature of EVM instructions:
Regular Opcodes (1 byte)
ADD, MUL, STOP, JUMPDEST, etc.
└─ Next PC = Current PC + 1
PUSH Instructions (1 + N bytes)
PUSH1: opcode (1 byte) + data (1 byte) = 2 bytes total
PUSH2: opcode (1 byte) + data (2 bytes) = 3 bytes total
...
PUSH32: opcode (1 byte) + data (32 bytes) = 33 bytes total
Next PC = Current PC + 1 + N (where N = push width)
Example:
const code = Bytecode ( "0x7F" + "FF" . repeat ( 32 ) + "01" );
// PUSH32 (0x7F) + 32 bytes data + ADD (0x01)
console . log ( code . getNextPc ( 0 )); // 33 (skip PUSH32 + 32 bytes)
console . log ( code . getNextPc ( 33 )); // 34 (ADD is 1 byte)
Usage Patterns
Manual Iteration
const code = Bytecode ( bytecode );
let pc = 0 ;
while ( pc !== undefined && pc < code . size ()) {
const instruction = code . at ( pc );
console . log ( `PC ${ pc } : ${ instruction } ` );
pc = code . getNextPc ( pc );
}
For iteration, prefer using bytecode.scan() which handles this automatically. Use getNextPc() for cases requiring manual PC control.
Jump Target Calculation
// Calculate all possible next PCs from a JUMPI
function getPossibleNextPcs (
code : BrandedBytecode ,
jumpiPc : number ,
jumpTarget : number
) : number [] {
const nextPcs : number [] = [];
// Fallthrough path (condition false)
const fallthrough = code . getNextPc ( jumpiPc );
if ( fallthrough !== undefined ) {
nextPcs . push ( fallthrough );
}
// Jump path (condition true)
nextPcs . push ( jumpTarget );
return nextPcs ;
}
const jumpiAt = 10 ;
const target = 50 ;
const paths = getPossibleNextPcs ( code , jumpiAt , target );
console . log ( `JUMPI at PC ${ jumpiAt } can go to: ${ paths . join ( ', ' ) } ` );
Instruction Boundary Validation
// Check if PC is at an instruction boundary (not in PUSH data)
function isInstructionStart ( code : BrandedBytecode , pc : number ) : boolean {
if ( pc === 0 ) return true ; // Entry point is always instruction start
// Walk backward to find the instruction that contains or precedes this PC
for ( let checkPc = pc - 1 ; checkPc >= 0 ; checkPc -- ) {
const nextPc = code . getNextPc ( checkPc );
if ( nextPc === undefined ) continue ;
if ( nextPc === pc ) {
// Found an instruction that ends exactly at pc
return true ;
} else if ( nextPc > pc ) {
// Found an instruction that spans past pc (we're in PUSH data)
return false ;
}
}
return false ;
}
console . log ( isInstructionStart ( code , 0 )); // true (entry)
console . log ( isInstructionStart ( code , 1 )); // false (inside PUSH1 data)
console . log ( isInstructionStart ( code , 2 )); // true (next instruction)
Disassembly with PC Tracking
const code = Bytecode ( bytecode );
const disassembly : string [] = [];
let pc = 0 ;
while ( pc !== undefined && pc < code . size ()) {
const opcode = code . raw ()[ pc ];
const instruction = Opcode . getName ( opcode );
// Get PUSH data if applicable
if ( Opcode . isPush ( opcode )) {
const pushSize = opcode - 0x5F ; // PUSH1 = 0x60, size = 1
const data = code . raw (). slice ( pc + 1 , pc + 1 + pushSize );
const value = bytesToHex ( data );
disassembly . push ( ` ${ pc } : ${ instruction } 0x ${ value } ` );
} else {
disassembly . push ( ` ${ pc } : ${ instruction } ` );
}
pc = code . getNextPc ( pc );
}
console . log ( disassembly . join ( ' \n ' ));
Coverage Tracking
// Track which PCs were executed
const executedPcs = new Set < number >();
// Simulate execution with PC tracking
function simulateExecution ( code : BrandedBytecode , startPc : number = 0 ) {
let pc : number | undefined = startPc ;
while ( pc !== undefined ) {
executedPcs . add ( pc );
const opcode = code . raw ()[ pc ];
// Handle control flow
if ( opcode === Opcode . JUMP ) {
// Would need stack to determine target
break ;
} else if ( opcode === Opcode . STOP || opcode === Opcode . RETURN ) {
break ;
}
pc = code . getNextPc ( pc );
}
}
simulateExecution ( code );
// Calculate coverage
const allPcs = new Set < number >();
let pc = 0 ;
while ( pc !== undefined && pc < code . size ()) {
allPcs . add ( pc );
pc = code . getNextPc ( pc );
}
const coverage = ( executedPcs . size / allPcs . size ) * 100 ;
console . log ( `PC coverage: ${ coverage . toFixed ( 1 ) } %` );
Edge Cases
At End of Bytecode
const code = Bytecode ( "0x00" ); // STOP
const nextPc = code . getNextPc ( 0 );
console . log ( nextPc ); // undefined (no next instruction)
Invalid PC
const nextPc = code . getNextPc ( 9999 );
console . log ( nextPc ); // undefined (PC out of bounds)
Truncated PUSH
// Malformed bytecode: PUSH2 with only 1 byte of data
const malformed = Bytecode ( "0x6100" ); // PUSH2 0x00 (missing 1 byte)
const nextPc = code . getNextPc ( 0 );
// Implementation dependent: may return PC past end or undefined
For malformed bytecode (truncated PUSH instructions), behavior is implementation-defined. Always validate bytecode before processing.
PC in Middle of PUSH Data
// PUSH2 0x1234 at PC 0 means:
// PC 0: PUSH2 opcode
// PC 1: 0x12 (data byte 1)
// PC 2: 0x34 (data byte 2)
// PC 3: next instruction
const nextFromOpcode = code . getNextPc ( 0 ); // 3 (correct)
const nextFromData1 = code . getNextPc ( 1 ); // undefined or error (invalid PC)
const nextFromData2 = code . getNextPc ( 2 ); // undefined or error (invalid PC)
Only call getNextPc() from instruction start positions. Calling from within PUSH data is undefined behavior.
getNextPc() is O(1) - constant time:
Reads single byte at current PC
Calculates width based on opcode
Returns PC + 1 + width
No caching or pre-analysis required.
Comparison with scan()
Feature getNextPc() scan() Use case Manual PC control Automatic iteration Returns Next PC number Instruction object PUSH data Returns PC after data Includes parsed value Performance O(1) per call O(1) per instruction Ease of use Low-level High-level
Use getNextPc() when:
Building custom traversal logic
Implementing jump analysis
Need fine-grained PC control
Working with PC-based data structures
Use scan() when:
Standard iteration over instructions
Need instruction metadata (opcode, type, value)
Want fusion detection
Don’t need manual PC manipulation
See Also
scan - High-level iterator (recommended for most use cases)
getBlock - Get block containing a PC
parseInstructions - Parse all instructions to array
Opcode - Opcode utilities for determining instruction width